概述

描述

  • 定义对象间的一种一对多依赖关系,使得每当一个对象状态发生改变时,其相关依赖对象皆得到通知并被自动更新。观察者模式又叫做

    • 发布-订阅(Publish/Subscribe)模式
    • 模型-视图(Model/View)模式
    • 源-监听器(Source/Listener)模式
    • 从属者(Dependents)模式。

套路

  • 抽象目标(Subject)

    被观察的目标,每个目标都可以有任何数量的观察者。抽象目标提供一个接口,可以增加和删除观察者对象。
  • 具体目标(ConcreteSubject)

    具体目标持有状态,当内部状态改变时,向所有观察者发出通知。同时向观察者提供用于查询状态的接口。
  • 抽象观察者(Observer)

    为所有的具体观察者定义一个接口,在得到目标通知时更新自己。
  • 具体观察者(ConcreteObserver)

    持有具体目标的引用。实现抽象观察者角色所要求的更新接口,以便使本身的状态与目标状态协调。

使用场景

  • 一个抽象模型有两个方面,其中一个方面依赖于另一个方面。将这些方面封装在独立的对象中使它们可以各自独立地改变和复用。
  • 一个对象的改变将导致其他一个或多个对象也发生改变,而不知道具体有多少对象将发生改变,可以降低对象之间的耦合度。
  • 一个对象必须通知其他对象,而并不知道这些对象是谁。
  • 需要在系统中创建一个触发链,A对象的行为将影响B对象,B对象的行为将影响C对象……,可以使用观察者模式创建一种链式触发机制。
  • 示例,凡是涉及到一对一或者一对多的对象交互场景都可以使用观察者模式。
    • 电子商务网站可以在执行发送操作后给用户多个发送商品打折信息
    • 可用于各种消息分发,如游戏活动通知、队伍副本攻略进度
    • UE4中的委托代理、蓝图中的事件调度器dispatcher、按键事件绑定、碰撞事件绑定
    • MVC 架构模式就应用了观察者模式——多个 view 注册监听 model

优缺点

  • 优点

    • 观察者模式可以实现表示层和数据逻辑层的分离,并定义了稳定的消息更新传递机制,抽象了更新接口,使得可以有各种各样不同的表示层作为具体观察者角色。
    • 观察者模式在观察目标和观察者之间建立一个抽象的耦合。
    • 观察者模式支持广播通信。
    • 观察者模式符合“开闭原则”的要求。
  • 缺点
    • 如果一个观察目标对象有很多直接和间接的观察者的话,将所有的观察者都通知到会花费很多时间。
    • 如果在观察者和观察目标之间有循环依赖的话,观察目标会触发它们之间进行循环调用,可能导致系统崩溃。
    • 观察者模式没有相应的机制让观察者知道所观察的目标对象是怎么发生变化的,而仅仅只是知道观察目标发生了变化。
    • 订阅后如果不进行取消订阅操作容易引起内存泄露

UE4 实践

  • 不使用 UE4 中的委托代理,自己实现一个消息通知

  • 做一个核辐射监测和救援人员受其影响的例子(代码都写在头文件中,有些声明问题需自己解决)

  • 创建观察者抽象类、具体类

    // 抽象观察者类
    UCLASS(Abstract)
    class DESIGNPATTERNS_API UObserver : public UObject
    {
    GENERATED_BODY()
    public: virtual void Update() PURE_VIRTUAL(UObserver::IsValid, );
    }; // 具体观察者类 —— 工作人员
    UCLASS(Blueprintable, BlueprintType)
    class DESIGNPATTERNS_API UWorker : public UObserver
    {
    GENERATED_BODY()
    public: // 设置核辐射监测对象
    void SetNuclearRadiation(UNuclearRadiation* pNuclearRadiation) {
    m_pNuclearRadiation = pNuclearRadiation;
    } virtual void Update() override {
    if (m_pNuclearRadiation)
    {
    int32 CurrentLevel = m_pNuclearRadiation->GetRadiationDegree();
    if (CurrentLevel < 1)
    {
    UE_LOG(LogTemp, Warning, TEXT(__FUNCTION__" %s报告: 生命体征正常"), *this->GetName());
    }
    else if (CurrentLevel < 2)
    {
    UE_LOG(LogTemp, Warning, TEXT(__FUNCTION__" %s报告: 轻级损伤"), *this->GetName());
    }
    else if (CurrentLevel < 4)
    {
    UE_LOG(LogTemp, Warning, TEXT(__FUNCTION__" %s报告: 中级损伤"), *this->GetName());
    }
    else if (CurrentLevel < 6)
    {
    UE_LOG(LogTemp, Warning, TEXT(__FUNCTION__" %s报告: 重级损伤"), *this->GetName());
    }
    else if (CurrentLevel < 8)
    {
    UE_LOG(LogTemp, Warning, TEXT(__FUNCTION__" %s报告: 极重级损伤"), *this->GetName());
    }
    else
    {
    UE_LOG(LogTemp, Warning, TEXT(__FUNCTION__" %s已无生命体征"), *this->GetName());
    this->ConditionalBeginDestroy();
    // 不要在此处取消订阅,否则影响TArray遍历
    }
    }
    } private:
    UNuclearRadiation* m_pNuclearRadiation;
    };
  • 创建目标抽象类 、具体类

    • 观察者可以用TArray 或者 TMap 存储
    // 抽象目标类
    UCLASS(Abstract)
    class DESIGNPATTERNS_API USubject : public UObject
    {
    GENERATED_BODY()
    public: // 添加订阅者
    virtual void BindNotify(UObserver* Observer) {
    if (!ObserverList.Contains(Observer)) {
    ObserverList.Add(Observer);
    UE_LOG(LogTemp, Warning, TEXT(__FUNCTION__" %s 已订阅"), *Observer->GetName());
    // 第一次绑定 获取当前数据
    Observer->Update();
    }
    } // 移除订阅者
    virtual void UnbindNotify(UObserver* Observer) {
    if (ObserverList.Contains(Observer)) {
    UE_LOG(LogTemp, Warning, TEXT(__FUNCTION__" %s 已取消订阅"), *Observer->GetName());
    ObserverList.Remove(Observer);
    }
    } // 通知
    virtual void Notify() {
    for (auto It = ObserverList.CreateConstIterator(); It; It++)
    {
    if (IsValid(*It))
    {
    (*It)->Update();
    }
    }
    } protected:
    TArray<UObserver*> ObserverList;
    }; // 具体目标类 —— 辐射地带、辐射监测
    UCLASS(Blueprintable, BlueprintType)
    class DESIGNPATTERNS_API UNuclearRadiation : public USubject
    {
    GENERATED_BODY()
    public:
    // 更新辐射程度
    void UpdateRadiationDegree(int32 pLevel) {
    m_pLevel = pLevel;
    Notify();
    } // 获取辐射程度
    int32 GetRadiationDegree() {
    return m_pLevel;
    } private:
    // 辐射程度 1-9级
    int32 m_pLevel;
    };
  • 调用测试

    // 调用测试用的Actor
    UCLASS()
    class DESIGNPATTERNS_API AObserverActor : public AActor
    {
    GENERATED_BODY()
    public: void BeginPlay() override { // 创建核辐射监测
    UNuclearRadiation* NuclearRadiation = NewObject<UNuclearRadiation>(); // 创建救援人员、监测核辐射程度
    UWorker* Worker0 = NewObject<UWorker>();
    Worker0->SetNuclearRadiation(NuclearRadiation);
    NuclearRadiation->BindNotify(Worker0); UWorker* Worker1 = NewObject<UWorker>();
    Worker1->SetNuclearRadiation(NuclearRadiation);
    NuclearRadiation->BindNotify(Worker1); UWorker* Worker2 = NewObject<UWorker>();
    Worker2->SetNuclearRadiation(NuclearRadiation);
    NuclearRadiation->BindNotify(Worker2); // 核辐射升级
    NuclearRadiation->UpdateRadiationDegree(1);
    NuclearRadiation->UpdateRadiationDegree(2);
    NuclearRadiation->UpdateRadiationDegree(3); // 有人先出来了,取消核辐射继续影响
    NuclearRadiation->UnbindNotify(Worker2); // 核辐射继续升级
    NuclearRadiation->UpdateRadiationDegree(5);
    NuclearRadiation->UpdateRadiationDegree(7);
    NuclearRadiation->UpdateRadiationDegree(9); GEngine->ForceGarbageCollection(true);
    }
    };
  • 调式输出

    LogTemp: Warning: USubject::BindNotify Worker_0 已订阅
    LogTemp: Warning: UWorker::Update Worker_0报告: 生命体征正常 LogTemp: Warning: USubject::BindNotify Worker_1 已订阅
    LogTemp: Warning: UWorker::Update Worker_1报告: 生命体征正常 LogTemp: Warning: USubject::BindNotify Worker_2 已订阅
    LogTemp: Warning: UWorker::Update Worker_2报告: 生命体征正常 LogTemp: Warning: UWorker::Update Worker_0报告: 轻级损伤
    LogTemp: Warning: UWorker::Update Worker_1报告: 轻级损伤
    LogTemp: Warning: UWorker::Update Worker_2报告: 轻级损伤 LogTemp: Warning: UWorker::Update Worker_0报告: 中级损伤
    LogTemp: Warning: UWorker::Update Worker_1报告: 中级损伤
    LogTemp: Warning: UWorker::Update Worker_2报告: 中级损伤 LogTemp: Warning: UWorker::Update Worker_0报告: 中级损伤
    LogTemp: Warning: UWorker::Update Worker_1报告: 中级损伤
    LogTemp: Warning: UWorker::Update Worker_2报告: 中级损伤 LogTemp: Warning: USubject::UnbindNotify Worker_2 已取消订阅 LogTemp: Warning: UWorker::Update Worker_0报告: 重级损伤
    LogTemp: Warning: UWorker::Update Worker_1报告: 重级损伤 LogTemp: Warning: UWorker::Update Worker_0报告: 极重级损伤
    LogTemp: Warning: UWorker::Update Worker_1报告: 极重级损伤 LogTemp: Warning: UWorker::Update Worker_0已无生命体征
    LogTemp: Warning: UWorker::Update Worker_1已无生命体征

参考

【UE4 设计模式】观察者模式 Observer Pattern的更多相关文章

  1. 设计模式-观察者模式(Observer Pattern)

    观察者模式(Observer Pattern):定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象.这个主题对象在状态发生变化时,会通知所有观察者对象,使他们能够自动更新自己. 观察者 ...

  2. 设计模式 - 观察者模式(Observer Pattern) 详细说明

    观察者模式(Observer Pattern) 详细说明 本文地址: http://blog.csdn.net/caroline_wendy/article/details/26583157 版权全部 ...

  3. 设计模式 - 观察者模式(Observer Pattern) 详细解释

    观察者模式(Observer Pattern) 详细解释 本文地址: http://blog.csdn.net/caroline_wendy/article/details/26583157 版权全部 ...

  4. 设计模式 - 观察者模式(Observer Pattern) Java内置 用法

    观察者模式(Observer Pattern) Java内置 用法 本文地址: http://blog.csdn.net/caroline_wendy/article/details/26601659 ...

  5. C#设计模式——观察者模式(Observer Pattern)1

    一.概述在软件设计工作中会存在对象之间的依赖关系,当某一对象发生变化时,所有依赖它的对象都需要得到通知.如果设计的不好,很容易造成对象之间的耦合度太高,难以应对变化.使用观察者模式可以降低对象之间的依 ...

  6. C#设计模式——观察者模式(Observer Pattern)

    一.概述在软件设计工作中会存在对象之间的依赖关系,当某一对象发生变化时,所有依赖它的对象都需要得到通知.如果设计的不好,很容易造成对象之间的耦合度太高,难以应对变化.使用观察者模式可以降低对象之间的依 ...

  7. 23种设计模式--观察者模式-Observer Pattern

    一.观察者模式的介绍      观察者模式从字面的意思上理解,肯定有两个对象一个是观察者,另外一个是被观察者,观察者模式就是当被观察者发生改变得时候发送通知给观察者,当然这个观察者可以是多个对象,在项 ...

  8. 乐在其中设计模式(C#) - 观察者模式(Observer Pattern)

    原文:乐在其中设计模式(C#) - 观察者模式(Observer Pattern) [索引页][源码下载] 乐在其中设计模式(C#) - 观察者模式(Observer Pattern) 作者:weba ...

  9. 二十四种设计模式:观察者模式(Observer Pattern)

    观察者模式(Observer Pattern) 介绍定义对象间的一种一对多的依赖关系,以便当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并自动刷新. 示例有一个Message实体类,某些对象 ...

  10. java设计模式--观察者模式(Observer)

    java设计模式--观察者模式(Observer) java设计模式--观察者模式(Observer) 观察者模式的定义: 定义对象间的一种一对多的依赖关系.当一个对象的状态发生改变时,所有依赖于它的 ...

随机推荐

  1. 20210720 noip21

    又是原题,写下题解吧 Median 首先时限有 2s(学校评测机太烂,加到 4s 了),可以放心地筛 \(1e7\) 个质数并算出 \(s_2\),然后问题变为类似滑动求中位数.发现 \(s_2\) ...

  2. 显示锁lock

    一.内置锁sync 和 显示锁lock概念 1.synv锁又叫内置锁,不能中断,拿不到无限等待即阻塞: java自带关键字: 隐式可重入: 重入锁:锁对应对象要多次调用对应方法,如递归 2. lock ...

  3. Tomcat部署与优化

    目录: 一.Tomcat概述 二.Tomcat 服务部署 三.Tomcat 虚拟主机配置 四.Tomcat 优化 一.Tomcat概述 Tomcat是Java语言开发的,Tomcat服务器是-个免费的 ...

  4. SpringBoot自定义初始化Bean+HashMap优化策略模式实践

    策略模式:定义了算法族,分别封装起来,让它们之间可以互相替换,此模式让算法的变化独立于使用算法的客户. 传统的策略模式一般是创建公共接口.定义公共方法-->然后创建实体类实现公共接口.根据各自的 ...

  5. Mac上Markdown的使用

    Markdown是什么,且听我快快道来. 20年前,我第一次接触互联网,当时还是用 28.8k的猫拨号. 我从一本<电脑报>附赠的光盘里,找到了 台湾版的"烘培机"(烘 ...

  6. 迷宫2----BFS

    题目 :蒜头君在你的帮助下终于逃出了迷宫,但是蒜头君并没有沉浸于喜悦之中,而是很快的又陷入了思考,从这个迷宫逃出的最少步数是多少呢?输入格式第一行输入两个整数 n 和 m,表示这是一个 n×m 的迷宫 ...

  7. 5.10学习总结——Activity的跳转和传值

    使用sharedpreference是对信息的存储,也可以进行传值,今天通过查找资料,学习了Activity的跳转和传值方法. 跳转 1.显示跳转 4种方法 1 2 3 4 5 6 7 8 9 10 ...

  8. Elasticsearch -head 查询报 406错误码

    问题:利用Elasticsearch -head插件不能查看数据或者在Elasticsearch -linux的curl命令操作时总是提示: {"error":"Cont ...

  9. python三种导入模块的方法

    做为python初学者,有时候搞不清楚导入模块的作用. 直接导入模块 通常模块为一个文件,直接使用import来导入就好了.可以作为module的文件类型有".py"." ...

  10. python 函数高级功能

    闭包 我们可以将闭包理解为一种特殊的函数,这种函数由两个函数的嵌套组成,且称之为外函数和内函数,外函数返回值是内函数的引用,此时就构成了闭包. # 闭包 # 外部函数的参数被内部函数引用,内部函数对外 ...