设计模式之禅——观察者模式

article/2025/8/22 15:23:44

    观察者模式是一种比较常见的设计模式,在安卓源码中也应用得很多,如baseAdapter、相关触发器机制。

    首先从一个情景入手,战国时期,李斯(观察者)作为秦国宰相希望监视(观察)韩非子(被观察者),当韩非子吃饭、玩乐、参与国家大事时李斯都能够知道。很容易想到一种实现,在韩非子的类中定义相关属性,例如boolean类型的isPlaying,表示韩非子是否在玩乐,当韩非子玩乐时改变属性值并提供getter、setter。实现代码可如下:

public class HanFeiZi {private boolean isPlaying=false;public boolean isPlaying() {return isPlaying;}public void setPlaying(boolean isPlaying) {this.isPlaying = isPlaying;}public void haveFun() {isPlaying=true;}
}
李斯 为了观察韩信的一举一动,需要创建一个间谍(spy)对象,而间谍对象就是一直盯着韩非子不停地获取韩非子的相关属性,如果发生了变化则通知李斯。代码实现上建立一个线程死循环检索韩非子相关属性,并关联李斯对象,发生变化则通知李斯。这样子可以实现效果,但是从资源上看死循环一直占有资源,影响程序运行效率,从设计模式上看,面向过程而非面向对象。

    上述实现方式的思路是观察者去不断尝试被观察者是否发生改变,观察者模式理解的关键是被观察者发生改变时主动去通知观察者:“我发生改变了”。

    根据此思想可以写出如下代码:

public void haveFun() {liSi.update();}
    当韩非子玩乐时告诉相关联的李斯对象,此时也能实现效果。但此时又存在问题,如果监视韩非子的不止李斯一个人,还有李斯1,李斯2……此时需要关联很多的对象,并且李斯关心的不只是韩非子的玩乐,此时按照上述原则会导致代码耦合冗余,违背开闭原则。

    此时,经过循序渐进的尝试,可以看一下观察者模式的类图。


包括四个对象,如下:

Subject又称Observable,被观察者接口,能够动态地添加、删除观察者,并且能够通知观察者被观察者的更新。

Observer,观察者接口,观察者接收到更新信息后调用update进行信息处理。

ConcreteSubject:实现被观察者接口,定义自身业务逻辑,并决定对哪些事件进行通知。

ConcreteObserver:实现观察者接口,观察者在接收到消息后的反应不同,有各自的逻辑处理。

    重新设定一个场景,女孩子吃零食时触发男朋友和女孩子妈妈不同的反应。我们可以逐步实现这个场景。

    首先定义观察者接口:

public interface Observer {void update(Observable observable,String msg);
}
    提供update函数对接收到的信息进行处理。

    紧接着实现被观察者接口:

public interface Observable {void addObserver(Observer observer);void deleteObserver(Observer observer);void notifyObservers(String msg);
}
提供对观察者的添加、删除和消息通知。

    实现女孩类:

public class GirlFriend implements Observable,GirlAction{private ArrayList<Observer> observerList=new ArrayList();public void addObserver(Observer observer) {observerList.add(observer);}public void deleteObserver(Observer observer) {observerList.remove(observer);}public void notifyObservers(String msg) {for(Observer observer:observerList)observer.update(this, msg);}public void eat() {System.out.println("女孩自己讲:我吃零食了");notifyObservers("女孩讲:我吃零食了");}/*** @param args*/public static void main(String[] args) {}
}
    女孩类中有一个私有成员observerList作为观察者集合用来管理观察者。基于面向接口编程的理念,实现女孩的行为接口:

public interface GirlAction {void eat();
}
    此时,创建男朋友观察者类:

public class BoyFriend implements Observer{public void update(Observable observable, String msg) {// TODO Auto-generated method stubSystem.out.println("男朋友接收到信息:"+msg);System.out.println("男朋友说:随便吃,我养你");}}
    其中自定义接收到信息后的应对逻辑。接着创建妈妈观察者类:

public class Mother implements Observer {public void update(Observable observable, String msg) {// TODO Auto-generated method stubSystem.out.println("妈妈接收到信息:"+msg);System.out.println("妈妈说:吃太多,嫁不出去");}}
    此时观察者、被观察者均已创建。在被观察者的main函数中注册观察者:

                Observer boyFriend=new BoyFriend();Observer mother=new Mother();GirlFriend girlFriend=new GirlFriend();girlFriend.addObserver(boyFriend);girlFriend.addObserver(mother);girlFriend.eat();
    此时,所有代码完成。运行结果如下:

女孩自己讲:我吃零食了
男朋友接收到信息:女孩讲:我吃零食了
男朋友说:随便吃,我养你
妈妈接收到信息:女孩讲:我吃零食了
妈妈说:吃太多,嫁不出去
    以上即为一个观察者模式的简单实现。


观察者模式的优点:

1.观察者和被观察者之间抽象耦合,不管是增加观察者还是被观察者都很容易扩展。

2.形成了触发机制,例如打猎导致一只母鹿死亡,母鹿死亡导致小鹿饿死,小鹿饿死引发秃鹰争抢……


观察者模式缺点:

1.开发效率:一个被观察者、多个观察者导致开发调试比较复杂

2.运行效率:消息的通知默认是顺序执行,上一个消息处理的崩溃会导致下一个消息无法传达,可以使用异步方式(线程)解决。


观察者模式使用场景:

1.存在关联行为的场景

2.事件存在多级触发的场景

3.跨系统的消息交换场景,如消息队列处理机制。需要注意的是安卓的handler处理机制仍是使用的while(true)方式。


观察者模式注意事项:

如果一个观察者同时具有被观察者的身份,很容易造成a触发b,b触发c陷入回调循环中。所以建议观察者模式中最多存在一个对象既是观察者又是被观察者,即一条消息最多被转发一次(传递两次)。例如:A->B->C,B作为观察者获得A传来的消息,作为被观察者传递消息给C。


观察者模式扩展:

1.为了更加符合单一职责原则,可被观察者抽象出一个父类实现观察者管理和消息通知。

2.java中提供了java.util.Observable接口,我们不需要自定义实现系统的即可。


观察者模式在实际项目中的扩展:

1.观察者被观察者之间的消息沟通

实际项目中,观察者获取消息时会接收两个参数,一个为Observable类型的被观察者对象,一个是被观察者生成封装好的Javabean对象,封装了相关信息。

2.观察者消息的处理方式

如果观察者对于消息的处理逻辑存在耗时操作会导致被观察者对象执行消息通知时间增长。此时可以使用两种技术改善,第一种使用多线程的异步框架,第二种使用缓存技术同步矿建预先准备好相关资源。

    特记下,以备后日回顾。


http://chatgpt.dhexx.cn/article/E7l7ev7H.shtml

相关文章

《设计模式之禅》-策略模式

策略模式 定义 定义一组算法&#xff0c;将每个算法都封装起来&#xff0c;并且使它们之间可以互换 策略模式有三个角色 1.Strategy 抽象策略角色 策略&#xff0c;算法家族的抽象&#xff0c;通常为接口&#xff0c;定义每个策略或算法必须具有的方法和属性。 2.Concrete…

《设计模式之禅》-原型模式

原型模式属于比较简单的设计模式&#xff0c;所以使用次数也非常的多 定义&#xff1a; 用原型实例指定创建对象的种类&#xff0c;并且通过拷贝这些原型创建新的对象 原型模式的通用代码&#xff1a; public class PrototypeClass implements Cloneable {Overridepublic Pr…

设计模式之禅——门面模式

门面模式【Facade Pattern】也叫外观模式&#xff0c;是一种比较常用的封装模式&#xff0c;其定义如下&#xff1a; 【要求一个子系统的外部与其内部的通信必须通过一个统一的对象进行。门面模式提供一个高层次的接口&#xff0c;使得子系统更容易使用】 门面模式注重“统一…

设计模式之禅读后感

过早优化是万恶之源——Unix编程思想 让正确的程序更快&#xff0c;要比让快速的程序正确容易得多。 文章目录 前言六大设计原则*单一职责原则**里氏替换原则**依赖倒置原则**接口隔离原则**迪米特法则**开闭原则* 二十三种设计模式创建型模式*单例模式**工厂方法模式**抽象工厂…

设计模式之禅笔记

设计模式PK 创建类模式PK 工厂方法模式 VS 建造者模式 工厂方法模式注重的是整体对象的创建方法。建造者模式注重的是部件构建的过程。 工厂方法模式的对象粒度较粗&#xff0c;建造者模式的产品对象粒度较细。 抽象工厂模式 VS 建造者模式 抽象工厂模式也是注重产品的整…

《设计模式之禅》(第2版)

第一部分 大旗不挥 谁敢冲锋 —— 6大设计原则全新解读 第1章 单一职责原则 单一职责原则&#xff0c;Single Responsibility Principle&#xff0c;简称SRP&#xff0c;有且只有一个原因引起类/接口的变更&#xff1b;即一个接口/类只有一个职责&#xff0c;只负责一件事…

设计模式之禅《一》 大旗不挥,谁敢冲锋 ——6大设计原则

设计模式之禅《一》大旗不挥&#xff0c;谁敢冲锋 ——6大设计原则 《一》 六大原则 一&#xff1a;单一职责原则 1、单一职责原则最难划分的就是职责 2、有两个可以变化的原因放到了一个接口中&#xff0c;这就为以后的变化带来了风险。 3、对于单一职责原则&#xff0c;…

设计模式之禅(六大设计原则)

1.单一职责原则(Single Responsibility Principle) 也就是职责划分要明确&#xff0c;单一职责原则提出了一个编写程序的标准&#xff0c;用“职责”或者“变化原因”来衡量接口或者类设计的是否优良&#xff0c;但是“职责”或者“变化原因”都是不可度量的&#xff0c;因项目…

什么是ASCII码

ASCII码英文全称America Standard Code for Information Interchange&#xff0c;中文意思&#xff1a;美国信息交换标准码。它已被国际标准化组织&#xff08;ISO&#xff09;定为国际标准&#xff0c;称为ISO 646标准。适用于所有拉丁文字字母&#xff0c;ASCII码有7位码和8位…

最全ASCii 码表和说明

A的ASCII码是65&#xff0c;a的ASCII码是97。 ASCII码表中&#xff0c;小写字母排在大写字母的后面&#xff0c;一个字母的大小写数值相差32&#xff0c;一般知道大写字母的ASCII码数值&#xff0c;其对应的小写字母的ASCII码数值就算出来了&#xff0c;是大写字母的ASCII码数…

ASCII码

字符编码即ASCII码&#xff08;American Standard Code for Information Interchange,美国信息交换标准码&#xff0c;国际通用的计算机内的字符数字的二进制编码&#xff09;&#xff0c;它是7位二进制编码&#xff0c;总共有128个符号&#xff0c;包括英文26个大写字母&#…

什么叫ASCII码

一个字符A&#xff0c;以 字符和整型都可以输出。因为字符A也是以二进制存储&#xff0c;这个二进制对应一个整数 那究竟什么是ASCII码&#xff1f; ASCII不是一个值&#xff0c;而是一种规定。 ASCII规定了不同字符是使用哪个整数值表示 它规定&#xff1a; A --65 B --…

关于ASCII码的转换

目录 ASCII码表ASCII码与字符之间的转换 ASCII码表 ASCII码与字符之间的转换 python中chr()和ord()两个函数可以实现ASCII码与字符之间的转换&#xff0c; 两者之间的关系 转换举例 stringbsuahisas num_list[ord(i) for i in string] str_list[chr(i) for i in num_list] p…

ASCII码介绍

ASCII码 概念 ASCII(American Standard Code for Information Interchange)的缩写&#xff08;美国标准信息交换代码&#xff09;&#xff0c;已被国际标准化组织ISO采纳&#xff0c;作为国际通用的信息交换标准代码。 诞生背景 计算机对数据的识别、运算和存储都建立在二进制…

ASCII编码

ASCII编码 ASCII编码的概念对控制字符的解释详细说明 ASCII编码的概念 计算机是以二进制的形式来存储数据的&#xff0c;它只认识 0 和 1 两个数字&#xff0c;我们在屏幕上看到的文字&#xff0c;在存储之前都被转换成了二进制&#xff08;0和1序列&#xff09;&#xff0c;在…

ascii码a和A为什么差32而不是26

ascii码a和A为什么差32而不是26&#xff1f; A的ASCII码是65&#xff0c;a的ASCII码是97&#xff1b; ASCII码表中&#xff0c;一个字母的大小写数值相差32&#xff0c;一般知道大写字母的ASCII码数值&#xff0c;其对应的小写字母的ASCII码数值就算出来了&#xff0c;是大写字…

ASCII码详解

ASCII简介 ASCII 码使用指定的 7 位或 8 位二进制数组合来表示 128 或 256 种可能的字符。标准 ASCII 码也叫基础ASCII码&#xff0c;使用 7 位二进制数来表示所有的大写和小写字母&#xff0c;数字 0 到 9、标点符号&#xff0c; 以及在美式英语中使用的特殊控制字符。其中&a…

a到z的ascii码值是多少_a和A对应的ASCII码数值分别是多少啊?

展开全部 A的ASCII码是65&#xff0c;a的ASCII码是97。 ASCII码表中&#xff0c;小写字母32313133353236313431303231363533e78988e69d8331333366303066排在大写字母的后面&#xff0c;一个字母的大小写数值相差32&#xff0c;一般知道大写字母的ASCII码数值&#xff0c;其对应…

输出ASCII码

输出字符的ASCII码 从键盘输入一个字符&#xff0c;按规定格式输出这个字符及它的ASCII码。例如&#xff1a; 输入 A 输出 “The ASCII of A is 65”.//输出的数值在双引号以内// 代码&#xff1a; ​ #include <stdio.h>int main() {char a;scanf("…

ascii码中的A的代码是什么?

ASCII&#xff08;American Standard Code for Information Interchange&#xff0c;美国标准信息交换代码&#xff09;是基于拉丁字母的一套电脑编码系统&#xff0c;主要用于显示现代英语和其他西欧语言。它是现今最通用的单字节编码系统&#xff0c;并等同于国际标准ISO/IEC…