常见代理模式
设计模式(Design Pattern)是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结。
使用设计模式是为了可重用代码、让代码更容易被他人理解、提高代码的可靠性。
创建者模式
特点:
将对象的创建与使用分离开;
使用者通过某种方式获取已经创建好的对象,而不关注如何创建;
一. 单例模式
单例模式属于创建型模式;
单例模式保证两点:
①只能他自己创建这个类的对象,
②只能创建一个对象;
保证第一点:
构造是private
私有的,只能由自己去创建本类对象;同时这个类提供了一种公共的访问其唯一的对象的方式,外界不需要去实例化该类的对象;
保证第二点:
单线程:类中的成员变量为static
的(类级别的变量),类加载时,静态变量是唯一的,所以保证只创建一个对象;
多线程:使用 synchronized
锁来保证;
1.1 饿汉式
饿汉式:【类加载】 导致单例对象被创建;
实现步骤:
- 创建
private
私有构造方法,保证①只能自己创造对象; - 在本类 声明 本类成员变量,用
static
静态修饰,保证②只能创建一个,并new对象 - 提供公共的get访问方法,让外界获取对象
(1) 静态成员变量
【类加载时】会自动完成静态成员变量初始化(对应类加载的链接-准备阶段);
new 对象才会触发普通实例变量的初始化;(执行类加载的链接-解析和 初始化阶段);
测试:
由于构造方法是私有的,无法创建对象来调用,所以通过类直接调用公共方法!
(2) 静态代码块
1.2 懒汉式
懒汉式:类加载不会导致该单例对象被创建,而是 【首次使用该对象时】才会创建;
(1) 线程不安全(普通)
实现步骤:
- 创建 私有构造方法,保证 ①只能自己创造对象
- 在本类 声明 本类成员变量,为
static
静态,只是声明,不new对象,保证②只能创建一个 - 提供公共访问方法,让外界首次使用时创建对象
问题:当有多个线程调用getInstance公共方法时,就可能创造多个对象,所以线程不安全;
(2) 静态内部方式
- 实例由内部类创建,由于JVM在加载外部类的过程中不会加载静态内部类! 只有内部类的属性/方法在被调用时才会被加载!并初始化属性;
- 静态属性被
static
修饰,保证只被实例化一次,并且严格保证实例化顺序;
(3) 线程安全(synchronized)
要保证线程安全,就要保证创造对象的那个方法具有原子性;即同一时刻只能有一个线程去访问资源;
这里使用 Synchronized
对公共方法加锁;
问题:整个getInstacne方法具有原子性,效率低
(4) 线程安全(双重检查锁)
由于懒汉模式中,getInstance方法大部分是读操作,读操作是线程安全的,所以没必要让每个线程都必须持有锁才能调用该方法,所以调整加锁的时机,由此产生了一种新的实现模式:双重检查锁模式;
注意:
这里要防止空指针异常,所以对实例变量使用 volatile
关键字修饰,保证可见性和禁止指令重排;
使用场景:
1.需要生成唯一序列的环境
2.需要频繁实例化然后销毁的对象。
3.创建对象时耗时过多或者耗资源过多,但又经常用到的对象。
如:系统的回收站、任务管理器;
总结
- 创建者模式:创建对象和使用分离开,,使用者不需要关心对象如何创建;
- 单例模式满足两点:①只能由自己来创建对象 ②只创建一个对象
- 单例模式分为:饿汉式、懒汉式,饿汉即类加载的时候就创建对象,懒汉式指使用的时候才创建对象;
- 饿汉式实现:
1.创建私有构造保证①只能由自己创建
2.声明本类的静态成员变量保证②只创建一个,并new对象
3.公共方法return对象 - 懒汉式实现(线程不安全):
1.创建私有构造保证①只能由自己创建
2.声明静态成员变量保证②只创建一个,不new对象
3.公共方法中先判断对象有没有,没有则new对象 - 懒汉式(线程安全):使用synchronized对公共方法加锁,使公共方法具有原子性,只有一个线程能访问公共方法;
- 为提高性能,使用双重检查锁,在公共方法中先普通判断对象是否存在,存在则直接return了;然后再用synchronized锁定 单例类.class,再判断对象如果为null才new对象;
二. 工厂模式
开闭原则:对扩展开放, 对修改关闭;
引入:
在java中,万物皆对象,这些对象 都需要创建,如果创建的时候直接new对象,就会对该对象耦合严重;
假如要更换对象,所有new对象的地方都要修改一遍,者违背了软件设计的开闭原则。
如果使用工厂来生产对象,我们就只和工厂打交道就可以了,彻底和对象解耦合,如果要更换对象,直接在【工厂内】更换对象即可。
优点:解耦合
2.1 简单工厂模式
简单工厂不是一种设计模式,而像一种编程习惯;
概括:
在客户和产品类之间增加一个工厂类,让创建对象的逻辑在工厂类中,而不是在客户这边,降低客户代码的耦合性;
缺点是工厂类依然和产品类耦合,不满足开闭原则;
结构:
1.抽象产品类:定义产品的规范,描述产品的特性和功能; (咖啡类)
2.具体产品类:实现/继承 抽象产品的子类; (美式咖啡、拿铁咖啡)
3.简单工厂类:提供了创建产品的方法,调用者通过改方法来获取产品对象;
案例:咖啡店
原来:咖啡店(客户)通过咖啡类创造对象;
简单工厂模式改进:
此时不需要咖啡店(客户)自己去new对象,而是调用简单工厂里的方法去new 咖啡对象;
抽象产品类:咖啡类
具体产品类:具体咖啡类继承抽象产品类;
拿铁咖啡:
美式咖啡:
简单工厂类:用来生产具体产品
声明方法,创建具体产品对象;
咖啡店(客户):只需要调用工厂的方法就可以创建具体产品对象;
测试:
打印:
优缺点:
优点:
将客户和产品类解耦合,降低了客户代码修改的可能性;
缺点:
工厂对象和产品对象耦合;
增加新产品时还是需要修改工厂类的代码,违背了“开闭原则”(对修改关闭,对扩展开放)。
静态工厂
简单工厂类:
对之前的简单工厂模式稍稍改动,将工厂类的方法变成static静态的;此时【客户这边】不再需要创建对象,而是直接用工厂类调用;
客户类:
总结
- 简单工厂模式就是在客户和产品类之间增加一个工厂类,让工厂类去创建具体产品对象,这样就能降低客户和产品类之间的耦合;
- 结构主要是:1.抽象产品类,2.具体产品类,3.简单工厂类;4.客户
- 在简单工厂类中声明方法 去创建具体产品对象,客户创建工厂类对象(或者类调用静态方法),调用方法就可以创建具体产品对象;
2.2 工厂方法模式
引入:解决简单工厂模式工厂类和产品类耦合的问题,完全遵循开闭原则;
概念:定义一个抽象工厂接口,是用于创建对象的接口
,让子类(具体工厂类)决定实例化哪个产品类对象。
工厂方法使一个产品类的实例化延迟到其抽象工厂的子类中;
结构:
1.抽象工厂接口:接口提供了创建产品的抽象方法,调用者通过它访问具体工厂的工厂方法来创建产品(多态)。
抽象工厂有多个具体的工厂子类,每个具体工厂常见对应的产品对象;
2.具体工厂类:主要是实现抽象工厂接口中的抽象方法,创建具体产品对象。
3.抽象产品类:定义了产品的规范,描述了产品的主要特性和功能。(咖啡类)
4.具体产品类:实现了抽象产品角色所定义的接口,由具体工厂来创建,它同具体工厂之间一一对应。(美式咖啡)
使用多态,让新的具体工厂类实现抽象工厂,这样就能在不改变抽象工厂和用户的情况下去扩展;
案例:咖啡店
抽象产品类:咖啡类
具体产品类:具体咖啡类继承抽象产品类;
拿铁咖啡:
美式咖啡:
抽象工厂(接口)
:
定义一个创建对象的抽象方法,由子类去实现;
具体工厂类:
一个具体工厂类对应一个具体产品类;
实现 抽象工厂接口,专门用来生产具体产品;
客户类(咖啡店):
创建抽象工厂类型的成员变量;
传入 具体工厂对象,赋值给成员变量(多态);
使用成员变量的方法创建产品对象,此时并不知道具体是什么对象;
即客户这边并不创造具体的产品对象,将客户和产品对象解耦合。
测试:
创建咖啡店(客户)对象;
创建具体工厂对象,将对象传入咖啡店的set方法去初始化抽象接口类型的成员变量;
再调用咖啡店的方法点咖啡;
此时如果要添加一种咖啡产品:
1.创建具体产品类;
2.创建对应的具体工厂子类实现抽象工厂类;
而抽象工厂类和客户类都不需要修改;----------------------- 符合开闭原则;
优点:
1.将工厂类和具体产品解耦合;
2.在系统增加新的产品时,只需要添加具体产品类和对应的具体工厂类,使用了多态
,无须对原工厂进行任何修改,满足开闭原则(对扩展开放,对修改关闭);
缺点:
每增加一个产品就要增加一个具体产品类和一个对应的具体工厂类,类太多容易引起JVM元空间OOM
;
总结
- 为解决简单工厂模式中工厂类和产品类耦合的问题,使用工厂方法模式
- 结构主要是:1.抽象工厂接口 2.具体工厂类 3.抽象产品类 4.具体产品类 5.客户
- 过程:
1.在抽象工厂接口定义一个创建对象的抽象方法
2.一个具体工厂类对应一个具体产品类,具体工厂类实现抽象工厂接口,重写抽象方法去创建具体的产品对象;
3.在客户类中:声明抽象工厂接口类型的成员变量,传入具体工厂类对象(参数为多态)初始化成员变量,再调用这个成员变量去创建具体的产品类,此时从语法上并不知道具体是什么对象,这取决于传入的具体工厂对象; - 这样就把工厂和具体产品类解耦合,有新的产品时,只需要传入新的具体工厂对象,就能创建对应的具体产品;但不需要更改原有的工厂代码,符合开闭原则;
2.3 抽象工厂模式
引入:使用工厂方法模式时,每一个产品都要定义产品类和具体工厂类,类的数量太多容易引起元空间OOM
;
概念:
抽象工厂模式用于生产一个产品族(多个级别)的产品;
结构:(和工厂方法一样)
抽象工厂接口:提供了创建多个不同产品级别的创建方法;
具体工厂类:实现抽象工厂中的多个抽象方法,一个具体工厂类对应一个产品族
,去创建不同级别的具体产品;
抽象产品:定义了产品的规范,描述了产品的主要特性和功能,抽象工厂模式有多个抽象产品。
具体产品:实现了抽象产品角色所定义的接口,由具体工厂来创建,它和具体工厂之间是多对一的关系。
总结
- 工厂方法类中,具体工厂类对应一个产品;而在抽象工厂中,一个具体工厂类对应一个产品族,去创建同一个产品族中不同级别的产品;
- 例如:
中式和美式两个产品组,抽象工厂接口中定义主食、饮品两个级别;
两个具体工厂类对应中式和美式,中式的具体工厂类生产 包子+疙瘩汤,美式的具体工厂类生产 汉堡+可乐;
优点:
1.一定程度上减少类的数量,防止元空间OOM
2.保证客户端始终只使用同一个产品族
中的对象,
3.扩展:增加新的产品族很方便,只需要增加对应的具体工厂类。
缺点:
当产品族中需要增加一个新的产品时,所有的工厂类都需要进行修改。
使用场景:
1.当需要创建的对象是一系列相互关联或相互依赖的产品族
时,如电器工厂中的电视机、洗衣机、空调等。
2.系统中有多个产品族,但每次只使用某一族产品。如有人只喜欢穿某一个品牌的衣服和鞋。
结构型模式
结构型模式描述如何将类或对象按某种布局组成更大的结构;
一. 代理模式
二. 适配器模式
定义:
借助适配器将一个类的接口转换成客户希望的目标接口,使得原本由于接口不兼容而不能一起工作的那些类能一起工作。
结构:
目标接口:当前系统业务所依赖的接口,它可以是抽象类或接口。(两孔)
适配者类:它是被访问和适配的现存组件库中(已经存在的)的组件接口。(三孔)
适配器类:它是一个转换器,通过继承或引用适配者的对象,把 适配者接口 → 目标接口(客户使用),让客户按目标接口的格式访问适配者。
实现方式:定义一个适配器类来 实现 目标接口(两孔),同时又 继承 现有组件库中存在的适配者类(三孔);
2.1 类适配器模式
案例:读卡器
当前电脑只能读取SD卡(目标接口-两孔),而要读取TF卡(适配者类-三孔)中的内容的话就需要使用到适配器模式。
创建一个读卡器,将TF卡中的内容读取出来(TF→SD);
(1) 适配者
适配者接口:
TFCard接口:
有读、写两个抽象方法;
TFCard实现类:
实现TF卡的读写功能;
(2) 目标接口
SDCard接口:
也是读、写的抽象方法;
SDCard实现类:
重写抽象方法;
电脑类:
从SD卡(目标接口)中读取数据;
客户端:
创建电脑对象,放入SD卡对象;
(3) 定义适配器:
用适配器实现目标接口(SD业务接口),并继承适配者(TF卡)类;
适配器会重写目标接口的方法,然后在【方法内】调用 适配者的方法
(实现适配者→目标接口的转换)
用户:
由于适配器实现了目标接口,所以可以将适配器对象传给电脑的readSD方法,调用适配器的readSD方法,底层调用了readTF方法,实现转换;
类适配器的问题:
- 类适配器模式违背了
合成复用原则
(尽量聚合,而不继承)。 - 类适配器是客户类有一个接口规范的情况下可用,反之不可用。即目标接口SD接口一定要存在,如果只有目标类,则无法使用。
2.2 对象适配器模式
引入:
- 适配器不再继承适配者类,而是直接聚合,满足
合成复用原则
; - 解决了“目标接口必须存在的问题”:可以让是适配器类去继承目标接口实现类(由于适配者类是聚合到适配器类中的,所以可以去继承!),则目标接口可以不存在;
适配器类:
不继承适配者类,而是聚合即声明适配者类的成员变量,并用构造传入适配者类;
在适配器类中调用成员变量(适配者对象)的方法,实现转换(类似于静态代理的远程调用
);
用户:
由于适配器实现了目标接口,所以可以将适配器对象传给电脑的readSD方法,调用适配器的readSD方法,底层调用了readTF方法,实现转换;
效果:实现电脑从TF卡读取的功能;
总结
- 适配器模式用来将类的接口转换成客户希望的目标接口,使得不兼容的接口能一起工作;
- 分为类适配器和对象适配器
- 结构:1.目标接口 2.适配者类 3.适配器类
- 类适配器的实现:
1.定义一个适配器类,实现目标接口,并继承适配者类,
2.在适配器中重写目标接口的方法,在方法内调用适配器类的方法,以此实现转换!(适配者→目标接口) - 类适配器的问题:①违背“
合成复用原则”
②必须存在目标接口,如果只有目标类则无法使用 - 所以引入对象适配器:①将适配者对象聚合到适配器中,不再继承,符合”合成复用原则“ ②由于不继承适配者类,就可以继承目标类,目标接口可以不存在;
- 对象适配器的实现:
1.聚合
适配者对象,即创建适配者类的成员变量,通过构造传入适配者对象,使类鱼类之间耦合度降低;
2.适配器类重写目标接口方法,调用成员变量(适配者对象)的方法,实现转换(类似静态代理
); - 使用场景:①旧的系统和新系统接口不一致,不更改旧系统 ②使用第三方组件,接口不一致;
使用场景:
1.以前开发的系统存在满足新系统功能需求的类,但其接口同新系统的接口不一致,使用适配器让新旧系统进行对接,而不修改之前系统的代码;
2.使用第三方组件,但组件接口定义和自己要求的接口定义不同。
合成复用原则
即尽量使用聚合的方式去复用;
1.通过继承来进行复用的主要问题在于继承复用会破坏系统的封装性,因为继承会将父类的实现细节暴露给子类;
2.如果父类发生改变,那么子类的实现也必须改变;
所以尽量避免使用继承,而聚合的方式可以降低类与类之间的耦合性;
三. 外观模式(门面模式)
定义:通过为多个复杂的子系统提供一个 一致的接口
,而使这些子系统更加容易被访问。
该模式对外有一个 统一接口
,外部应用程序不用关心内部子系统的具体的细节,这样会大大降低应用程序的复杂度,提高了程序的可维护性。(类似封装)
例:
炒股肯定都会想如果有个懂行的帮帮手就好,基金就是个好帮手,它将投资者分散的资金集中起来,交由专业的经理人
进行管理,投资于股票、债券、外汇等领域,而基金投资的收益归持有者所有,管理机构收取一定比例的托管管理费用。
对于访问者,不需要知道子系统如何实现,只需要调用对外的接口即可访问;即只需要了解基金就可以,而基金经理人投资的是股票、债券还是外汇,访问者不需要关注;
结构:
外观类:为多个子系统对外提供一个共同的接口,用户只需要和外观角色交互。
子系统类:实现系统的部分功能,客户可以通过外观角色访问它并使用。
案例:智能家电
小明的爷爷已经60岁了,一个人在家生活:每次都需要打开灯、打开电视、打开空调;睡觉时关闭灯、
关闭电视、关闭空调;操作起来都比较麻烦。所以小明给爷爷买了智能音箱,可以通过语音直接控制这多个智能家电的开启和关闭。
用户只需要和智能音箱交互即可;
子系统:
电灯类:
电视类:
空调类:
智能音响类(外观类):
在外观类中声明子系统类型的成员变量,并在构造中创建所有子系统的对象初始化成员变量;
对外提供方法进行所有子系统的控制(一键打开、一键关闭);
客户:
只需要创建智能音响类,让音响去控制所有的子系统;
效果:
优缺点
优点:
降低了子系统与客户之间的耦合度,使得子系统的变化不会影响调用它的客户类。
对客户屏蔽了子系统组件,减少了客户处理的对象数目,并使得子系统使用起来更加容易。
缺点:
不符合开闭原则,修改很麻烦。
和代理模式区别?
代理模式针对的是单个对象,而外观模式针对的是所有子类;
使用场景:
1.对分层结构系统构建时,使用外观模式定义子系统中每层的入口点可以简化子系统之间的依赖关系。
即高层调用底层时,直接使用公共的接口,不依赖具体的实现,可以使得代码扩展性更好,简化依赖。
2.当一个复杂系统的子系统很多时,外观模式可以为系统设计一个简单的接口供外界访问。
3.当客户端与多个子系统之间存在很大的联系时,引入外观模式可将它们分离,消除客户端和子系统之间的耦合;
总结
- 外观模式就是为多个复杂的子系统提供一个对外的接口,让外界能够方便调用,而不用关心内部细节,类似封装;
- 结构:1.外观类 2.多个 子系统类
- 实现:
1.在外观类中声明所有子系统类型的成员变量,然后在构造中初始化这些成员变量;
2.在外观类中提供一个对外的方法,在方法内统一对所有子系统进行控制;
3.客户创建外观类对象,调用公共方法即可实现对子系统的统一控制; - 优点:降低子系统和客户的耦合,对客户屏蔽了子系统;
缺点:不符合开闭原则,修改麻烦 - 使用场景:
1.构建分层系统时,用外观模式定义每层的入口,简化依赖
2.复杂子系统太多,使用外观模式设计一个简单的访问接口
3.客户和子系统存在联系,使用外观模式将其分离,消除客户和子系统的耦合;
行为模式
行为型模式用于描述程序在运行时复杂的流程控制,即描述多个类或对象之间怎样相互协作共同完成单个对象都无法单独完成的任务,它涉及算法与对象间职责的分配。
行为型模式分为类行为模式和对象行为模式,前者采用继承机制来在类间分派行为,后者采用组合或聚
合在对象间分配行为。由于组合关系或聚合关系比继承关系耦合度低,满足“合成复用原则”,所以对象行为模式比类行为模式具有更大的灵活性;
除了模板设计模式和解释器模式是类行为模式,其他都是对象行为型模式;
一. 模板设计模式
在面向对象程序设计过程中,程序员常常会遇到这种情况:设计一个系统时知道了算法的骨架
,而且确定了这些步骤的执行顺序,但某些步骤的具体实现还未知,或者说某些步骤的实现与具体的
环境相关。
定义:
用模板方法定义一个操作的 算法骨架
,同时将算法的一些步骤延迟到子类中,使得子类可以不改变该算法结构的情况下重定义该算法的某些特定步骤(顺序固定)。
结构:
抽象类:
- 模板方法:定义了算法的
骨架
,即按某种顺序调用其包含的基本方法。 - 基本方法:是实现算法各个步骤的方法,是模板方法的组成部分。基本方法又可以分为:
①抽象方法:一个抽象方法由抽象类声明、由具体子类实现。
②具体方法:一个具体方法由一个抽象类或具体类声明并实现,其子类可以进行覆盖也可以直接继承。
具体子类:
实现抽象类中所定义的抽象方法,它们是一个顶级逻辑的组成步骤。
案例:炒菜
要用模板设计模式,则需要定义抽象类和具体子类;
抽象类中定义模板方法和基本方法;
模板方法:定义骨架即炒菜的顺序:倒油、热油、倒菜、倒佐料、翻炒(调用成员函数);
基本方法:重写到蔬菜和倒佐料;
抽象类:模板方法+基本方法
为了不让子类改变模板方法(骨架)的结构,需要加上final
修饰模板方法!
具体子类(爆炒包菜):
继承抽象类,并重写两个抽象方法;
具体子类(蒜蓉菜心):
继承抽象类,并重写两个抽象方法;
客户:
要炒菜,则用子类创建具体子类对象,调用继承来的模板方法
即可按顺序执行方法!
效果:按照骨架顺序执行方法!
对比外观模式: 外观模式对接外部系统,让外部去统一的控制多个子系统,而模版模式用于内部方法按顺序执行;
作用:后序再炒其他的蔬菜,只需要继承抽象类,然后根据需要去重写抽象方法就可以!调用模板方法就能按照骨架顺序执行!
优缺点
优点:
1.提高代码复用性:将相同部分的代码放在抽象类中,不同的代码放入子类
2.实现反转控制:通过父类调用子类的操作,通过子类的具体实现扩展不同的行为,实现反向控制。未来扩展只需要定义其他子类即可,不需要更改父类;
缺点:
1.对每个不同的实现都需要定义一个子类,会导致类的个数增加,系统更加庞大;
2.父类的抽象方法由子类实现,但是子类执行的结果会影响父类(模板方法),这是反转控制,代码阅读难度高;
使用场景:
1.算法的整体步骤固定(骨架),但个别又易变,所以用模板方法定义骨架顺序,然后用基本方法–抽象方法定义易变的部分让子类去实现;
2.需要通过子类来决定某个步骤是否执行,实现子类对父类的反向控制;(使用钩子函数isXx)
总结
- ①模板设计模式即确定了算法骨架,即程序执行的顺序已知,②但将一些步骤延迟到子类中去实现,让子类在不改变算法结构的情况下去定义特定的步骤;
- 结构:
1.抽象类: ① 模板方法 ②基本方法:抽象方法/具体方法
2.具体子类; - 实现:
1.在抽象类中,定义基本方法,分为具体方法和抽象方法,抽象方法会让子类去实现;
2.在抽象类中,定义模板方法即算法骨架,在模板方法中按算法顺序去调用基本方法,并且模板方法是用final
的(为了防止子类改变算法骨架)
3.让具体子类实现抽象类,重写抽象方法
4.客户只需要创建具体子类对象,调用继承来的模板方法
即可; - 优点:1.提高了代码的复用性,易扩展 2.通过子类重写抽象方法,实现了反转控制
缺点:子类执行会影响到父类,代码阅读难度大 - 使用场景:1.整体步骤固定,个别易变的情况
二. 观察者模式
又被称为发布-订阅模式,它定义了一种多对一的依赖关系,让多个观察者对象同时监听某一个主题对象。
结构:
抽象主题接口:抽象主题角色把所有观察者对象保存在一个集合里,每个主题都可以有任意数量的观察者,抽象主题提供一个接口,可以增加和删除观察者对象。
具体主题类:该角色将有关状态存入具体观察者对象,在具体主题的内部状态发生改变时,给所有注册过的观察者发送通知,观察者对象中的方法就会自动被调用。
抽象观察者:是观察者的抽象类,它定义了一个更新接口,使得在得到主题更改通知时更新自己。
具体观察者:实现抽象观察者定义的更新接口,以便在得到主题更改通知时更新自身的状态。
案例:微信公众号
抽象主题接口:
定义 添加观察者、删除观察者和notify()
三个方法;
具体主题类(公众号):
定义一个存储观察者的集合;
重写三个方法;
其中notify
方法:当主题类发生变化时,遍历观察者集合,调用所有观察者对象的update()
方法!
抽象观察者接口:
具体观察者类(微信用户):
客户:
结果:
优缺点:
优点:
1.降低了主题对象和观察者之间的耦合,两者之间是抽象耦合关系
2.主题对象发送通知,所有观察者都会收到信息,实现广播机制
;
缺点:
1.如果观察者非常多,那么所有的观察者收到主题对象发送的通知会耗时(遍历集合,最后的会比较慢被调用)
2.如果主题对象有循环依赖的话,那么主题对象发送通知会导致观察者循环调用,会导致系统崩溃;
使用场景:
1.对象之间存在一对多的关系,一个对象状态改变会影响其他对象
2.当一个抽象模型有两个方面,其中一个方面依赖于另一个方面时;
总结
- 观察者模式又被称为发布-订阅模式,定义了一种多对一的依赖关系,让多个观察者同时监听一个主题对象;
当主题对象发生变化,会调用notify()方法,底层会调用所有观察者的update方法; - 结构:抽象主题接口、具体主题类、抽象观察者接口、具体观察者类
- 实现:
1.抽象主题接口定义添加、删除、notify()三个方法
2.具体主题类重写方法,并声明一个集合
,在notify()中遍历集合调用其update();
3.抽象观察者接口定义update()方法;
4.具体观察者类重写update方法;
5.客户只需要创建主题对象,并创建观察者对象,将观察者对象添加到主题对象中,最后调用主题对象的notify方法,底层就会调用所有观察者的update()方法, 实现发布-订阅功能。