观察者模式介绍

news2025/2/23 1:13:43

目录

一、观察者模式介绍

1.1 观察者模式定义

1.2 观察者模式原理

1.2.1 观察者模式类图

1.2.2 模式角色说明

1.2.3 示例代码

二、观察者模式的应用

2.1 需求说明

2.2 需求实现

2.2.1 未使用设计模式

2.2.1.1 具体实现

2.2.2 适用观察者模式

2.2.2.1 优化调整说明

2.2.2.1 具体实现

2.2.2.1.1 事件监听接口

2.2.2.1.2 短信发送事件

2.2.2.1.3 MQ消息发送事件

2.2.2.1.4 事件处理类

2.2.2.1.5 开奖服务接口

2.2.2.1.6 开奖服务实现

2.2.2.1.7 测试类

三、观察者模式总结

3.1 观察者模式的优点

3.2 观察者模式的缺点

3.3 观察者模式常见的使用场景

3.4 JDK 中对观察者模式的支持


一、观察者模式介绍

1.1 观察者模式定义

观察者模式(observer pattern)的原始定义是:定义对象之间的一对多依赖关系,这样当一个对象改变状态时,它的所有依赖项都会自动得到通知和更新。


解释一下上面的定义: 观察者模式它是用于建立一种对象与对象之间的依赖关系,一个对象发生改变时将自动通知其他对象,其他对象将相应的作出反应。
在观察者模式中发生改变的对象称为观察目标,而被通知的对象称为观察者,一个观察目标可以应对多个观察者,而且这些观察者之间可以没有任何相互联系,可以根据需要增加和删除观察者,使得系统更易于扩展。


观察者模式的别名有发布-订阅(Publish/Subscribe)模式,模型-视图(ModelView)模式、源-监听(Source-Listener) 模式等。

观察者模式的应用场景非常广泛,小到代码层面的解耦,大到架构层面的系统解耦,再或者 一些产品的设计思路,都有这种模式的影子。


现在我们常说的基于事件驱动的架构,其实也是观察者模式的一种最佳实践。当我们观察某一个对象时,对象传递出的每一个行为都被看成是一个事件,观察者通过处理每一个事件来完成自身的操作处理。


生活中也有许多观察者模式的应用,比如 汽车与红绿灯的关系,'红灯停,绿灯行',在这个过程中交通信号灯是汽车的观察目标,而汽车是观察者。

1.2 观察者模式原理

1.2.1 观察者模式类图

1.2.2 模式角色说明

在观察者模式中有如下角色:

  • Subject:抽象主题(抽象被观察者)

抽象主题角色把所有观察者对象保存在一个集合里,每个主题都可以有任意数量的观察者,抽象主题提供一个
接口,可以增加和删除观察者对象。

  • ConcreteSubject:具体主题(具体被观察者)

该角色将有关状态存入具体观察者对象,在具体主题的内部状态发生改变时,给所有注册过的观察者发送通知。

  • Observer:抽象观察者

是观察者的抽象类,它定义了一个更新接口,使得在得到主题更改通知时更新自己。

  • ConcrereObserver:具体观察者

实现抽象观察者定义的更新接口,以便在得到主题更改通知时更新自身的状态。在具体观察者中维护一个指向具体目标对象的引用,它存储具体观察者的有关状态,这些状态需要与具体目标保持一致。

1.2.3 示例代码

package main.java.cn.test.observer.V1;

/**
 * @author ningzhaosheng
 * @date 2024/1/14 18:02:40
 * @description 抽象观察者
 */
public interface Observer {
    //update方法: 为不同观察者的更新(响应)行为定义相同的接口,不同的观察者对该方法有不同的实现
    public void update();
}

package main.java.cn.test.observer.V1;

/**
 * @author ningzhaosheng
 * @date 2024/1/14 18:03:10
 * @description 具体观察者
 */
public class ConcreteObserverOne implements Observer {
    @Override
    public void update() {
        //获取消息通知,执行业务代码
        System.out.println("ConcreteObserverOne 得到通知!");
    }
}

package main.java.cn.test.observer.V1;

/**
 * @author ningzhaosheng
 * @date 2024/1/14 18:03:49
 * @description 具体观察者
 */
public class ConcreteObserverTwo implements Observer {
    @Override
    public void update() {
        //获取消息通知,执行业务代码
        System.out.println("ConcreteObserverTwo 得到通知!");
    }
}

package main.java.cn.test.observer.V1;

/**
 * @author ningzhaosheng
 * @date 2024/1/14 18:04:25
 * @description 抽象目标类
 */
public interface Subject {
    void attach(Observer observer);

    void detach(Observer observer);

    void notifyObservers();
}

package main.java.cn.test.observer.V1;

import java.util.ArrayList;

/**
 * @author ningzhaosheng
 * @date 2024/1/14 18:05:03
 * @description 具体目标类
 */
public class ConcreteSubject implements Subject {
    //定义集合,存储所有观察者对象
    private ArrayList<Observer> observers = new ArrayList<>();

    //注册方法,向观察者集合中增加一个观察者
    @Override
    public void attach(Observer observer) {
        observers.add(observer);


    }

    //注销方法,用于从观察者集合中删除一个观察者
    @Override
    public void detach(Observer observer) {
        observers.remove(observer);
    }

    //通知方法
    @Override
    public void notifyObservers() {
        //遍历观察者集合,调用每一个观察者的响应方法
        for (Observer obs : observers) {
            obs.update();
        }
    }
}

package main.java.cn.test.observer.V1;

/**
 * @author ningzhaosheng
 * @date 2024/1/14 18:06:52
 * @description 测试类
 */
public class Test {
    public static void main(String[] args) {
        //创建目标类(被观察者)
        ConcreteSubject subject = new ConcreteSubject();
        //注册观察者类,可以注册多个
        subject.attach(new ConcreteObserverOne());
        subject.attach(new ConcreteObserverTwo());
        //具体主题的内部状态发生改变时,给所有注册过的观察者发送通知。
        subject.notifyObservers();
    }
}

二、观察者模式的应用

2.1 需求说明

接下来我们使用观察模式,来实现一个买房摇号的程序。摇号结束,需要通过短信告知用户摇号结果,还需要想MQ中保存用户本次摇号的信息。

2.2 需求实现

2.2.1 未使用设计模式

2.2.1.1 具体实现
package main.java.cn.test.observer.V2;

/**
 * @author ningzhaosheng
 * @date 2024/1/14 18:23:16
 * @description 开奖服务接口
 */
public interface LotteryService {
    //摇号相关业务
    public LotteryResult  lottery(String uId);
}

package main.java.cn.test.observer.V2;

import java.util.Date;

/**
 * @author ningzhaosheng
 * @date 2024/1/14 18:23:53
 * @description 开奖服务
 */
public class LotteryServiceImpl implements LotteryService {
    //注入摇号服务
    private DrawHouseService houseService = new DrawHouseService();

    @Override
    public LotteryResult lottery(String uId) {
        //摇号
        String result = houseService.lots(uId);
        //发短信
        System.out.println("发送短信通知用户ID为: " + uId + ",您的摇号结果如下: " + result);
        //发送MQ消息
        System.out.println("记录用户摇号结果(MQ), 用户ID:" + uId + ",摇号结果:" + result);
        return new LotteryResult(uId, result, new Date());
    }
}

package main.java.cn.test.observer.V2;

import java.util.Date;

/**
 * @author ningzhaosheng
 * @date 2024/1/14 18:22:13
 * @description 摇号结果
 */
public class LotteryResult {


    private String uId; // 用户id
    private String msg; // 摇号信息
    private Date dataTime; // 业务时间

    public LotteryResult(String uId, String msg, Date dataTime) {
        this.uId = uId;
        this.msg = msg;
        this.dataTime = dataTime;
    }

    public String getuId() {
        return uId;
    }

    public void setuId(String uId) {
        this.uId = uId;
    }

    public String getMsg() {
        return msg;
    }

    public void setMsg(String msg) {
        this.msg = msg;
    }

    public Date getDataTime() {
        return dataTime;
    }

    public void setDataTime(Date dataTime) {
        this.dataTime = dataTime;
    }

    @Override
    public String toString() {
        return "LotteryResult{" +
                "uId='" + uId + '\'' +
                ", msg='" + msg + '\'' +
                ", dataTime=" + dataTime +
                '}';
    }
}

package main.java.cn.test.observer.V2;

/**
 * @author ningzhaosheng
 * @date 2024/1/14 18:21:39
 * @description 模拟买房摇号服务
 */
public class DrawHouseService {
    //摇号抽签
    public String lots(String uId) {
        if (uId.hashCode() % 2 == 0) {
            return "恭喜ID为: " + uId + " 的用户,在本次摇号中中签! !";
        } else {
            return "很遗憾,ID为: " + uId + "的用户,您本次未中签!!";
        }
    }

}
package main.java.cn.test.observer.V2;

/**
 * @author ningzhaosheng
 * @date 2024/1/14 18:26:36
 * @description 测试类
 */
public class Test {
    public static void main(String[] args) {
        LotteryService ls = new LotteryServiceImpl();
        LotteryResult result = ls.lottery("1234567887654322");
        System.out.println(result.toString());
    }
}

2.2.2 适用观察者模式

2.2.2.1 优化调整说明

上面的摇号业务中,摇号、发短信、发MQ消息是一个顺序调用的过程,但是除了摇号这个核心功能以外,发短信与记录信息到MQ的操作都不是主链路的功能,需要单独抽取出来,这样才能保证在后面的开发过程中保证代码的可扩展性和可维护性。

2.2.2.1 具体实现
2.2.2.1.1 事件监听接口
package main.java.cn.test.observer.V3;

import main.java.cn.test.observer.V2.LotteryResult;

/**
 * @author ningzhaosheng
 * @date 2024/1/15 10:59:49
 * @description 事件监听接口
 */
public interface EventListener {
    void doEvent(LotteryResult result);
}

2.2.2.1.2 短信发送事件
package main.java.cn.test.observer.V3;

import main.java.cn.test.observer.V2.LotteryResult;

/**
 * @author ningzhaosheng
 * @date 2024/1/15 11:00:26
 * @description 短信发送事件
 */
public class MessageEventListener implements EventListener {
    @Override
    public void doEvent(LotteryResult result) {
        System.out.println("发送短信通知用户ID为: " + result.getuId() + ",您的摇号结果如下: " + result.getMsg());
    }
}

2.2.2.1.3 MQ消息发送事件
package main.java.cn.test.observer.V3;

import main.java.cn.test.observer.V2.LotteryResult;

/**
 * @author ningzhaosheng
 * @date 2024/1/15 11:01:06
 * @description MQ消息发送事件
 */
public class MQEventListener implements EventListener {
    @Override
    public void doEvent(LotteryResult result) {
        System.out.println("记录用户摇号结果(MQ), 用户ID:" + result.getuId() + ",摇号结果:" + result.getMsg());
    }
}

2.2.2.1.4 事件处理类
package main.java.cn.test.observer.V3;

import main.java.cn.test.observer.V2.LotteryResult;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * @author ningzhaosheng
 * @date 2024/1/15 11:01:46
 * @description 事件处理类
 */
public class EventManager {
    public enum EventType {
        MQ, Message
    }

    //监听器集合
    Map<Enum<EventType>, List<EventListener>> listeners = new
            HashMap<>();

    public EventManager(Enum<EventType>... operations) {
        for (Enum<EventType> operation : operations) {
            this.listeners.put(operation, new ArrayList<>());
        }
    }

    /**
     * 订阅
     *
     * @param eventType 事件类型
     * @param listener  监听
     */
    public void subscribe(Enum<EventType> eventType,
                          EventListener listener) {
        List<EventListener> users = listeners.get(eventType);
        users.add(listener);
    }

    /**
     * 取消订阅
     *
     * @param eventType 事件类型
     * @param listener  监听
     */
    public void unsubscribe(Enum<EventType>
                                    eventType, EventListener listener) {
        List<EventListener> users = listeners.get(eventType);
        users.remove(listener);
    }

    /**
     * 通知
     *
     * @param eventType 事件类型
     * @param result    结果
     */
    public void notify(Enum<EventType> eventType,
                       LotteryResult result) {
        List<EventListener> users = listeners.get(eventType);
        for (EventListener listener : users) {
            listener.doEvent(result);
        }
    }


}

2.2.2.1.5 开奖服务接口
package main.java.cn.test.observer.V3;

import main.java.cn.test.observer.V2.LotteryResult;

/**
 * @author ningzhaosheng
 * @date 2024/1/15 11:03:21
 * @description 开奖服务接口
 */
public abstract class LotteryService {
    private EventManager eventManager;

    public LotteryService() {
        //设置事件类型
        eventManager = new EventManager(EventManager.EventType.MQ, EventManager.EventType.Message);
        //订阅
        eventManager.subscribe(EventManager.EventType.Message, new MessageEventListener());
        eventManager.subscribe(EventManager.EventType.MQ, new MQEventListener());
    }

    public LotteryResult lotteryAndMsg(String uId) {
        LotteryResult result = lottery(uId);
        //发送通知
        eventManager.notify(EventManager.EventType.Message, result);
        eventManager.notify(EventManager.EventType.MQ, result);
        return result;
    }

    public abstract LotteryResult lottery(String uId);
}

2.2.2.1.6 开奖服务实现
package main.java.cn.test.observer.V3;

import main.java.cn.test.observer.V2.DrawHouseService;
import main.java.cn.test.observer.V2.LotteryResult;

import java.util.Date;

/**
 * @author ningzhaosheng
 * @date 2024/1/15 11:05:14
 * @description 开奖服务
 */
public class LotteryServiceImpl extends LotteryService {
    //注入摇号服务
    private DrawHouseService houseService = new DrawHouseService();

    @Override
    public LotteryResult lottery(String uId) {
        //摇号
        String result = houseService.lots(uId);
        return new LotteryResult(uId, result, new Date());

    }
}

2.2.2.1.7 测试类
package main.java.cn.test.observer.V3;

import main.java.cn.test.observer.V2.LotteryResult;

/**
 * @author ningzhaosheng
 * @date 2024/1/15 11:07:14
 * @description 测试类
 */
public class Test {
    public static void main(String[] args) {
        LotteryService ls = new LotteryServiceImpl();
        LotteryResult result = ls.lotteryAndMsg("1234567887654322");
        System.out.println(result);
    }
}

三、观察者模式总结

3.1 观察者模式的优点

  • 降低了目标与观察者之间的耦合关系,两者之间是抽象耦合关系。
  • 被观察者发送通知,所有注册的观察者都会收到信息【可以实现广播机制】

3.2 观察者模式的缺点

  • 如果观察者非常多的话,那么所有的观察者收到被观察者发送的通知会耗时
  • 如果被观察者有循环依赖的话,那么被观察者发送通知会使观察者循环调用,会导致系统崩溃

3.3 观察者模式常见的使用场景

  • 当一个对象状态的改变需要改变其他对象时。

比如,商品库存数量发生变化时,需要通知商品详情页、购物车等系统改变数量。

  • 一个对象发生改变时只想要发送通知,而不需要知道接收者是谁。

比如,订阅微信公众号的文章,发送者通过公众号发送,订阅者并不知道哪些用户订阅了公众号。

  • 需要创建一种链式触发机制时。

比如,在系统中创建一个触发链,A 对象的行为将影响 B 对象,B 对象的行为将影响 C 对象……这样通过观察者模式能够很好地实现。

  • 微博或微信朋友圈发送的场景。

这是观察者模式的典型应用场景,一个人发微博或朋友圈,只要是关联的朋友都会收到通知;一旦取消关注,此人以后将不会收到相关通知。

  • 需要建立基于事件触发的场景。

比如,基于 Java UI 的编程,所有键盘和鼠标事件都由它的侦听器对象和指定函数处理。当用户单击鼠标时,订阅鼠标单击事件的函数将被调用,并将所有上下文数据作为方法参数传递给它。

3.4 JDK 中对观察者模式的支持

JDK中提供了Observable类以及Observer接口,它们构成了JDK对观察者模式的支持。

  • java.util.Observer 接口:

该接口中声明了一个方法,它充当抽象观察者,其中声明了一个update方法。

void update(Observable o, Object arg);
  •  java.util.Observable 类:

充当观察目标类(被观察类) , 在该类中定义了一个Vector集合来存储观察者对象.下面是它最重要的 3 个方法。

void addObserver(Observer o) 方法:用于将新的观察者对象添加到集合中。

void notifyObservers(Object arg) 方法:调用集合中的所有观察者对象的 update方法,通知它们数据发生改变。通常越晚加入集合的观察者越先得到通知。

void setChange() 方法:用来设置一个 boolean 类型的内部标志,注明目标对象发生了变化。当它为true时,notifyObservers() 才会通知观察者。

用户可以直接使用Observer接口和Observable类作为观察者模式的抽象层,再自定义具体观察者类和具体观察目标类,使用JDK中提供的这两个类可以更加方便的实现观察者模式。

好了,本次分享就到这里,欢迎大家继续阅读《设计模式》专栏其他设计模式内容,如果有帮助到大家,欢迎大家点赞+关注+收藏,有疑问也欢迎大家评论留言!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1391896.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

保送阿里云的云原生学习路线

近期好多人都有咨询学习云原生有什么资料。与其说提供资料不如先说一说应该如何学习云原生。 Linux基础知识&#xff1a;云原生技术通常在Linux环境中运行&#xff0c;因此建议首先掌握Linux的基础知识&#xff0c;包括命令行操作、文件系统、权限管理等。 容器化技术&#x…

【不用找素材】ECS 游戏Demo制作教程(2) 1.16

一、知识点补充 1.工程内部 上一篇最后一步运行时&#xff0c;突然发现 变成52:1了&#xff0c;难道每次baking都是随机的&#xff1f; 破案了&#xff0c;52是index索引&#xff0c;1是version版本号 如果您在场景视图中看不到实体&#xff0c;但仍然可以在游戏视图中看到…

速通——决策树(泰坦尼克号乘客生存预测案例)

一、决策树 1、概述 树中每个内部节点表示一个特征上的判断&#xff0c;每个分支代表一个判断结果的输出&#xff0c;每个叶子节点代表一种分类结果 2、建立过程 1. 特征选择&#xff1a;选取有较强分类能力的特征。 2. 决策树生成&#xff1a;根据选择的特征生成决策树。 3.…

AI扩展手写数字识别应用(二)

理解代码 输入处理 在新应用的代码部分&#xff0c;和我们在手写数字识别课程介绍的代码比起来&#xff0c;差别最大的地方就在于如何处理输入。在上个案例中&#xff0c;我们只需要简单地将正方形区域中的图像格式调整一下&#xff0c;即可用作MNIST模型的输入。而在本文的案…

Angular系列教程之自定义指令

文章目录 前言指令的基本概念在模板中使用指令总结 前言 在Angular中&#xff0c;指令是一种非常强大的工具&#xff0c;用于扩展HTML元素的功能和行为。它们允许我们创建可重用的组件&#xff0c;并在应用程序中的多个地方使用它们。本文将介绍Angular指令的基础知识&#xf…

1 python计算机基础

计算机基础和环境搭建 1 计算机基础和环境搭建1.计算机基础1.1 基本概念1.2 编程语言1.3 编译器/解释器 2.学习编程的本质3.Python的介绍3.1 语言的分类3.2 Python3.3 Python的解释器种类&#xff08;了解&#xff09;3.4 CPython解释器的版本 4.环境搭建4.1 安装Python解释器4…

【Filament】材质系统

1 前言 本文主要介绍 Filament 的材质系统&#xff0c;官方介绍详见 → Filament Materials Guide。材质系统中会涉及到一些空间和变换的知识点&#xff0c;可以参考&#xff1a;【Unity3D】空间和变换、【Unity3D】Shader常量、变量、结构体、函数、【OpenGL ES】MVP矩阵变换、…

Flume 之自定义Sink

1、简介 前文我们介绍了 Flume 如何自定义 Source&#xff0c; 并进行案例演示&#xff0c;本文将接着前文&#xff0c;自定义Sink&#xff0c;在这篇文章中&#xff0c;将使用自定义 Source 和 自定义的 Sink 实现数据传输&#xff0c;让大家快速掌握Flume这门技术。 2、自定…

设计一个抽奖系统

&#x1f44f;作者简介&#xff1a;大家好&#xff0c;我是爱吃芝士的土豆倪&#xff0c;24届校招生Java选手&#xff0c;很高兴认识大家&#x1f4d5;系列专栏&#xff1a;Spring原理、JUC原理、Kafka原理、分布式技术原理、数据库技术&#x1f525;如果感觉博主的文章还不错的…

排序算法9----计数排序(C)

计数排序是一种非比较排序&#xff0c;不比较大小 。 1、思想 计数排序又称为鸽巢原理&#xff0c;是对哈希直接定址法的变形应用。 2、步骤 1、统计数据&#xff1a;统计每个数据出现了多少次。&#xff08;建立一个count数组&#xff0c;范围从[MIN,MAX],MAX代表arr中…

韩愈和白居易,相交不相融的两位大伽

韩愈和白居易&#xff0c;一个百代文宗一个引领诗风&#xff0c;却关系平淡原因何在&#xff1f; 一位就是韩愈&#xff08;768年&#xff0d;824年&#xff09;&#xff0c;一位是白居易&#xff08;772年&#xff0d;846年&#xff09;&#xff0c;都是唐代各有建树的文学领…

2024年华数杯国际赛赛题浅析

21号完赛&#xff0c;28号出成绩的华数杯国际赛&#xff0c;作为美赛最合适的练手赛正式开赛。为了让大家更好地比赛&#xff0c;首先为大家带来本次竞赛两道题目的浅要解析。主要分析两道题目适合的群体&#xff0c;未来大家求解过程中可能遇到的问题。方便大家快速完成选题。…

【Proteus仿真】【Arduino单片机】汽车车窗除霜系统设计

文章目录 一、功能简介二、软件设计三、实验现象联系作者 一、功能简介 本项目使用Proteus8仿真Arduino单片机控制器&#xff0c;使用LCD1602显示模块、光线传感器、DS18B20温度传感器、PCF8691 ADC模块、继电器加热模块等。 主要功能&#xff1a; 系统运行后&#xff0c;LCD…

微软Power Platform使用Power Automate Desktop flow桌面流爬取京东商品信息

微软Power Platform使用Power Automate Desktop flow桌面流爬取京东商品信息 目录 微软Power Platform使用Power Automate Desktop flow桌面流爬取京东商品信息1、创建一个桌面流应用Desktop flow2、启动新的浏览器&#xff0c;跳转到我们需要的URL中3、在文本框中填充文字并点…

Android Studio 如何设置中文

Android Studio 是一个为 Adndroid 平台开发程序的集成开发环境&#xff08;IDE&#xff09;。 如何安装中文插件 在 Jetbrains 家族的插件市场上&#xff0c;是能够搜到语言包插件的&#xff0c;正常情况下安装之后只需要重启即可享受中文界面&#xff0c;可AndroidStudio 中…

基于vue+Spring Boot家政服务人员预约系统iph9d

通过对家政服务管理内容的学习研究&#xff0c;进而设计并实现一个家政服务系统。系统能实现的主要功能应包括即时通讯、通讯回复、预约订单、接单信息、服务费用管、服务评价的一些操作。还有可以正确的为用户服务&#xff0c;准确显示当前信息[5]。 开发软件有很多种可以用&…

【链路层】点对点协议 PPP

目录 1、PPP协议的特点 2、PPP协议的组成和帧格式 3、PPP协议的工作状态 目前使用得最广泛的数据链路层协议是点对点协议PPP(Point-to-Point Protocol)。 1、PPP协议的特点 我们知道&#xff0c;互联网用户通常都要连接到某个 ISP 才能接入到互联网。PPP 协议就是用户计算机…

本地一键部署grafana+prometheus

本地k8s集群内一键部署grafanaprometheus 说明&#xff1a; 此一键部署grafanaPrometheus已包含&#xff1a; victoria-metrics 存储prometheus-servergrafanaprometheus-kube-state-metricsprometheus-node-exporterblackbox-exporter grafana内已导入基础的dashboard【7个…

2024年华数杯国际赛A题赛题

问题A&#xff1a;来自日本的放射性废水 背景 2011年3月&#xff0c;日本东海岸发生的地震引发了福岛第一核电站的事故。一场大规模海啸摧毁了该核电站的冷却系统&#xff0c;导致三个核反应堆熔毁&#xff0c;核燃料碎片熔化。为了冷却熔化的核燃料&#xff0c;海水不断地注入…

数据仓库面试题

1 思维导图&数仓常见面试题 2 题目 1. 数据仓库是什么&#xff1f; 数据仓库是一个面向主题的&#xff08;订单、支付、退单等&#xff09;、集成的&#xff08;整合多个信息源的大量数据&#xff09;、非易失的&#xff08;一般不会进行删除和修改操作&#xff09;且随时…