设计模式
设计模式六大原则
单一职责原则
- 一个类只负责一个功能领域中的相应职责,就一个类而言,应该只有一个引起它变化的原因
- 是实现高内聚、低耦合的指导方针
- 解释:
- 高内聚
- 尽可能类的每个成员方法只完成一件事(最大限度的聚合)
- 模块内部的代码, 相互之间的联系越强,内聚就越高, 模块的独立性就越好
- 低耦合: 减少类内部,一个成员方法调用另一个成员方法, 不要有牵一发动全身
- 高内聚
开闭原则
- 对扩展开放,对修改关闭,在程序需要进行拓展的时候,不能去修改原有的代码,实现一个热插拔的效果
里氏替换原则LSP
- 任何基类可以出现的地方,子类一定可以出现
- 在程序中尽量使用基类类型来对对象进行定义,而在运行时再确定其子类类型,用子类对象来替换父类对象
- controller->service->dao
依赖倒转原
- 是开闭原则的基础,针对接口编程,依赖于抽象而不依赖于具体
- 高层模块不应该依赖低层模块,二者都应该依赖其抽象
接口隔离原则
- 客户端不应该依赖那些它不需要的接口
- 使用多个隔离的接口,比使用单个接口要好,降低类之间的耦合度
迪米特法则
- 最少知道原则,一个实体应当尽量少地与其他实体之间发生相互作用,使得系统功能模块相对独立
- 类之间的耦合度越低,就越有利于复用,一个处在松耦合中的类一旦被修改,不会对关联的类造成太大波及
- 通过引入一个合理的第三者来降低现有对象之间的耦合度
常见的三大设计模式分类
-
创建型模式
-
提供了一种在创建对象的同时隐藏创建逻辑的方式,使得程序在判断针对某个给定实例需要创建哪些对象时更加灵活
常用:工厂模式、抽象工厂模式、单例模式、建造者模式
不常用:原型模式
-
结构型模式
-
关注类和对象的组合。继承的概念被用来组合接口和定义组合对象获得新功能的方式
常用:适配器模式、桥接模式、装饰器模式、代理模式
不常用:组合模式、外观模式、享元模式、
-
行为型模式
-
特别关注对象之间的通信
常用:责任链模式、迭代器模式、观察者模式、状态模式、策略模式、模板模式
不常用:备忘录模式、命令模式
几乎不用:访问者模式、中介者模式、解释器模式
创建型设计模式
单例模式
懒汉
调用时加载
public class SingletonLazy {
private static SingletonLazy instance;
private SingletonLazy() {
}
public void process() {
System.out.println("调用方法成功");
}
public static SingletonLazy getInstance() {
if (instance == null) {
instance = new SingletonLazy();
}
return instance;
}
}
饿汉
提前创建
public class SingletonHungry {
private static SingletonHungry instance = new SingletonHungry();
private SingletonHungry(){}
public static SingletonHungry getInstance(){
return instance;
}
public void process(){
System.out.println("方法调用成功");
}
}
调用
public static void main(String[] args) {
SingletonLazy.getInstance().process();
SingletonHungry.getInstance().process();
}
扩展
- JDK中Runtime类 饿汉方式
- JDK中Desktop类 懒汉方式
工厂模式
工厂模式介绍:
它提供了一种创建对象的最佳方式,我们在创建对象时不会对客户端暴露创建逻辑,并且是通过使用一个共同的接口来指向新创建的对象
应用场景:
- 解耦:分离职责,把复杂对象的创建和使用的过程分开
- 复用代码 降低维护成本:
- 如果对象创建复杂且多处需用到,如果每处都进行编写,则很多重复代码,如果业务逻辑发生了改变,需用四处修改;
- 使用工厂模式统一创建,则只要修改工厂类即可,降低成本
简单工厂模式
通过传入相关的类型来返回相应的类,这种方式比较单 一,可扩展性相对较差;
-
简单工厂模式
- 又称静态工厂方法, 可以根据参数的不同返回不同类的实例,专门定义一个类来负责创建其他类的实例,被创建的实例通常都具有共同的父类
- 由于工厂方法是静态方法,可通过类名直接调用,而且只需要传入简单的参数即可
-
核心组成
- Factory:工厂类,简单工厂模式的核心,它负责实现创建所有实例的内部逻辑
- IProduct:抽象产品类,简单工厂模式所创建的所有对象的父类,描述所有实例所共有的公共接口
- Product:具体产品类,是简单工厂模式的创建目标
-
实现步骤
- 创建抽象产品类,里面有产品的抽象方法,由具体的产品类去实现
- 创建具体产品类,继承了他们的父类,并实现具体方法
- 创建工厂类,提供了一个静态方法createXXX用来生产产品,只需要传入你想产品名称
-
优点:
- 将对象的创建和对象本身业务处理分离可以降低系统的耦合度,使得两者修改起来都相对容易。
-
缺点
- 工厂类的职责相对过重,增加新的产品需要修改工厂类的判断逻辑,这一点与开闭原则是相违背
- 即开闭原则(Open Close Principle)对扩展开放,对修改关闭,程序需要进行拓展的时候,不能去修改原有的代码,实现一个热插拔的效果
- 将会增加系统中类的个数,在一定程度上增加了系统的复杂度和理解难度,不利于系统的扩展和维护,创建简单对象就不用模式
SimplePayFactory.createPay("Wechat").unifiedorder();
工厂方法模式
通过实现类实现相应的方法来决定相应的返回结果,这种方式的可扩展性比较强;
-
工厂方法模式
- 又称工厂模式,是对简单工厂模式的进一步抽象化,其好处是可以使系统在不修改原来代码的情况下引进新的产品,即满足开闭原则
- 通过工厂父类定义负责创建产品的公共接口,通过子类来确定所需要创建的类型
- 相比简单工厂而言,此种方法具有更多的可扩展性和复用性,同时也增强了代码的可读性
- 将类的实例化(具体产品的创建)延迟到工厂类的子类(具体工厂)中完成,即由子类来决定应该实例化哪一个类。
-
核心组成
- IProduct:抽象产品类,描述所有实例所共有的公共接口
- Product:具体产品类,实现抽象产品类的接口,工厂类创建对象,如果有多个需要定义多个
- IFactory:抽象工厂类,描述具体工厂的公共接口
- Factory:具体工场类,实现创建产品类对象,实现抽象工厂类的接口,如果有多个需要定义多个
PayFactory aliPayFactory = new AliPayFactory();
Pay aliPay = aliPayFactory.getPay();
aliPay.unifiedorder();
PayFactory wechatPayFactory = new WechatPayFactory();
Pay wechatPay = wechatPayFactory.getPay();
wechatPay.unifiedorder();
抽象工厂模式
基于上述两种模式的拓展,且支持细化产品
OrderFactory wechat = FactoryProducer.getFactory("WECHAT");
Pay pay = wechat.createPay();
pay.unifiedorder();
RefundFactory refund = wechat.createRefund();
refund.refund();
原型模式
-
原型设计模式Prototype
- 是一种对象创建型模式,使用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象,主要用于创建重复的对象,同时又能保证性能
- 工作原理是将一个原型对象传给那个要发动创建的对象,这个要发动创建的对象通过请求原型对象拷贝自己来实现创建过程
- 应该是最简单的设计模式了,实现一个接口,重写一个方法即完成了原型模式
-
核心组成
- Prototype: 声明克隆方法的接口,是所有具体原型类的公共父类,Cloneable接口
- ConcretePrototype : 具体原型类
- Client: 让一个原型对象克隆自身从而创建一个新的对象
-
应用场景
- 创建新对象成本较大,新的对象可以通过原型模式对已有对象进行复制来获得
- 如果系统要保存对象的状态,做备份使用
-
遗留问题:
-
通过对一个类进行实例化来构造新对象不同的是,原型模式是通过拷贝一个现有对象生成新对象的
-
浅拷贝实现 Cloneable,深拷贝是通过实现 Serializable 读取二进制流
-
拓展
- 浅拷贝
如果原型对象的成员变量是基本数据类型(int、double、byte、boolean、char等),将复制一份给克隆对象; 如果原型对象的成员变量是引用类型,则将引用对象的地址复制一份给克隆对象,也就是说原型对象和克隆对象的成员变量指向相同的内存地址 通过覆盖Object类的clone()方法可以实现浅克隆
- 深拷贝
无论原型对象的成员变量是基本数据类型还是引用类型,都将复制一份给克隆对象,如果需要实现深克隆,可以通过序列化(Serializable)等方式来实现
- 原型模式是内存二进制流的拷贝,比new对象性能高很多,使用的时候记得注意是选择浅拷贝还是深拷贝
-
-
优点
- 当创建新的对象实例较为复杂时,使用原型模式可以简化对象的创建过程,可以提高新实例的创建效率
- 可辅助实现撤销操作,使用深克隆的方式保存对象的状态,使用原型模式将对象复制一份并将其状态保存起来,以便在需要的时候使用恢复到历史状态
-
缺点
- 需要为每一个类配备一个克隆方法,对已有的类进行改造时,需要修改源代码,违背了“开闭原则”
- 在实现深克隆时需要编写较为复杂的代码,且当对象之间存在多重的嵌套引用时,需要对每一层对象对应的类都必须支持深克隆
建造者模式
建造者模式(Builder Pattern)
-
使用多个简单的对象一步一步构建成一个复杂的对象,将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示
-
允许用户只通过指定复杂对象的类型和内容就可以构建它们,不需要知道内部的具体构建细节
-
核心组成
- Builder:抽象建造者,定义多个通用方法和构建方法
- ConcreteBuilder:具体建造者,可以有多个
- Director:指挥者,控制整个组合过程,将需求交给建造者,由建造者去创建对象
- Product:产品角色
结构型设计模式
适配器模式
桥接模式
桥接设计模式
- 适配器模式类似,包括以后经常会遇到意思接近一样的设计模式,因为大神往往就是多个模式混用,且根据不同的场景进行搭配,桥接设计模式也是结构型模式
- 将抽象部分与实现部分分离,使它们都可以独立的变化
- 通俗来说,是通过组合来桥接其它的行为/维度
-
优点
- 抽象和实现的分离。
- 优秀的扩展能力,符合开闭原则
-
缺点
- 增加系统的理解与设计难度
- 使用聚合关联关系建立在抽象层,要求开发者针对抽象进行设计与编程,比如抽象类汽车,里面聚合了颜色类,有点像对象适配器
-
总结和对比
- 桥接模式和适配器模式用于设计的不同阶段,
- 桥接模式用于设计的前期,精细化的设计,让系统更加灵活
- 适配器模式用于设计完成之后,发现类、接口之间无法一起工作,需要进行填坑
- 适配器模式经常用在第三方API协同工作的场合,在功能集成需求越来越多的今天,这种模式的使用频度越来越高,包括有些同学听过 外观设计模式 ,这个也是某些场景和适配器模式一样
- 桥接模式和适配器模式用于设计的不同阶段,
组合模式
- 组合设计模式
- 又叫部分整体模式,将对象组合成树形结构以表示“部分-整体”的层次结构,可以更好的实现管理操作
- 组合模式使得用户可以使用一致的方法操作单个对象和组合对象
- 部分-整体对象的基本操作多数是一样的,但是应该还会有不一样的地方
- 核心:组合模式可以使用一棵树来表示
- 应用场景
- 银行总行,总行有前台、后勤、网络部门等,辖区下还有地方分行,也有前台、后勤、网络部门,最小的分行就没有子分行了
- 公司也是,总公司下有子公司,每个公司大部分的部门都类似
- 文件夹和文件,都有增加、删除等api,也有层级管理关系
- 当想表达对象的部分-整体的层次结构
- 当我们的要处理的对象可以生成一颗树形结构,我们要对树上的节点和叶子进行操作时,它能够提供一致的方式,而不用考虑它是节点还是叶子
- 角色
- 组合部件(Component):它是一个抽象接口,表示树根,例子:总行
- 合成部件(Composite):和组合部件类似,也有自己的子节点,例子:总行下的分行
- 叶子(Leaf):在组合中表示子节点对象,注意是没有子节点,例子:最小地方的分行
- 缺点
- 客户端需要花更多时间理清类之间的层次关系
- 优点
- 客户端只需要面对一致的对象而不用考虑整体部分或者节点叶子的问题
- 方便创建出复杂的层次结构
装饰器模式
- 装饰器设计模式(Decorator Pattern)
- 也叫包装设计模式,属于结构型模式,它是作为现有的类的一个包装,允许向一个现有的对象添加新的功能,同时又不改变其结构
- 给对象增加功能,一般两种方式 继承或关联组合,将一个类的对象嵌入另一个对象中,由另一个对象来决定是否调用嵌入对象的行为来增强功能,这个就是装饰器模式,比继承模式更加灵活
- 角色(装饰者和被装饰者有相同的超类(Component))
- 抽象组件(Component)
- 定义装饰方法的规范,最初的自行车,仅仅定义了自行车的API;
- 被装饰者(ConcreteComponent)
- Component的具体实现,也就是我们要装饰的具体对象
- 实现了核心角色的具体自行车
- 装饰者组件(Decorator)
- 定义具体装饰者的行为规范, 和Component角色有相同的接口,持有组件(Component)对象的实例引用
- 自行车组件 都有 名称和价格
- 抽象组件(Component)
-
优点
- 装饰模式与继承关系的目的都是要扩展对象的功能,但装饰模式可以提供比继承更多的灵活性。
- 使用不同的具体装饰类以及这些装饰类的排列组合,可以创造出很多不同行为的组合,原有代码无须改变,符合“开闭原则”
-
缺点
- 装饰模式增加了许多子类,如果过度使用会使程序变得很复杂 (多层包装)
- 增加系统的复杂度,加大学习与理解的难度
-
装饰器模式和桥接模式对比
-
相同点都是通过封装其他对象达到设计的目的,和对象适配器也类似,有时也叫半装饰设计模式
-
没有装饰者和被装饰者的主次区别,桥接和被桥接者是平等的,桥接可以互换,不用继承自同一个父类
比如例子里面的,可以是Phone持有Color,也可以是Color持有Phone
-
桥接模式不用使用同一个接口;装饰模式用同一个接口装饰,接口在父类中定义
-
代理模式
-
代理设计模式(Proxy Pattern)
- 为其他对象提供一种代理以控制对这个对象的访问,属于结构型模式。
- 客户端并不直接调用实际的对象,而是通过调用代理,来间接的调用实际的对象
-
应用场景
- 各大数码专营店,代理厂商进行销售对应的产品,代理商持有真正的授权代理书
- 客户端不想直接访问实际的对象,或者访问实际的对象存在困难,通过一个代理对象来完成间接的访问
- 想在访问一个类时做一些控制,或者增强功能
-
角色
- Subject: 抽象接口,真实对象和代理对象都要实现的一个抽象接口,好比销售数码产品
- Proxy: 包含了对真实对象的引用,从而可以随意的操作真实对象的方法,好比 代理加盟店
- RealProject :真实对象,好比厂商销售数码产品
- 优点
- 可以在访问一个类时做一些控制,或增加功能
- 操作代理类无须修改原本的源代码,符合开闭原则,系统具有较好的灵活性和可扩展性
- 缺点
- 增加系统复杂性和调用链路
- 和装饰器模式的区别:
- 代理模式主要是两个功能
- 保护目标对象
- 增强目标对象,和装饰模式类似了
- 代理模式主要是两个功能
外观模式
-
外观设计模式 Facade Pattern
- 门面模式,隐藏系统的复杂性,并向客户端提供了一个客户端可以访问系统的接口
- 定义了一个高层接口,这个接口使得这系统更加容易使用
-
角色
- 外观角色(Facade):客户端可以调用这个角色的方法,这个外观方法知道多个子系统的功能和实际调用
- 子系统角色(SubSystem):每个子系统都可以被客户端直接调用,子系统并不知道门面的存在,
- 优点
- 减少了系统的相互依赖,提高了灵活性
- 符合依赖倒转原则
- 针对接口编程,依赖于抽象而不依赖于具体
- 符合迪米特法则
- 最少知道原则,一个实体应当尽量少地与其他实体之间发生相互作用
- 缺点
- 增加了系统的类和链路
- 不是很符合开闭原则,如果增加了新的逻辑,需要修改facade外观类
享元模式-----------
-
享元设计模式(Flyweight Pattern)
- 属于结构型模式,主要用于减少创建对象的数量,以减少内存占用和提高性能, 它提供了减少对象数量从而改善应用所需的对象结构的方式。
- 享元模式尝试重用现有的同类对象,如果未找到匹配的对象,则创建新对象
-
角色
- 抽象享元角色:为具体享元角色规定了必须实现的方法,而外部状态就是以参数的形式通过此方法传入
- 具体享元角色:实现抽象角色规定的方法。如果存在内部状态,就负责为内部状态提供存储空间。
- 享元工厂角色:负责创建和管理享元角色。要想达到共享的目的,这个角色的实现是关键
- 客户端角色:维护对所有享元对象的引用,而且还需要存储对应的外部状态
-
优点
- 大大减少了对象的创建,降低了程序内存的占用,提高效率
-
缺点
- 提高了系统的复杂度,需要分离出内部状态和外部状态
-
注意划分内部状态和外部状态,否则可能会引起线程安全问题,必须有一个工厂类加以控制
-
享元设计模式和原型、单例模式的区别
- 原型设计模式是指定创建对象的种类,然后通过拷贝这些原型来创建新的对象。
- 单例设计模式保证一个类仅有一个实例
行为型设计模式
策略模式**
-
策略模式(Strategy Pattern)
- 定义一系列的算法,把它们一个个封装起来, 并且使它们可相互替换
- 淘宝天猫双十一,正在搞活动有打折的、有满减的、有返利的等等,这些算法只是一种策略,并且是随时都可能互相替换的, 我们就可以定义一组算法,将每个算法都封装起来,并且使它们之间可以互换
-
角色
- Context上下文:屏蔽高层模块对策略、算法的直接访问,封装可能存在的变化
- Strategy策略角色:抽象策略角色,是对策略、算法家族的抽象,定义每个策略或算法必须具有的方法和属性
- ConcreteStrategy具体策略角色:用于实现抽象策略中的操作,即实现具体的算法
- 优点
- 满足开闭原则,当增加新的具体策略时,不需要修改上下文类的代码,上下文就可以引用新的具体策略的实例
- 避免使用多重条件判断,如果不用策略模式可能会使用多重条件语句不利于维护,和工厂模式的搭配使用可以很好地消除代码if-else的多层嵌套(工厂模式主要是根据参数,获取不同的策略)
- 缺点
- 策略类数量会增多,每个策略都是一个类,复用的可能性很小
- 对外暴露了类所有的行为和算法,行为过多导致策略类膨胀
模板方法模式
- 角色
- 抽象模板(Abstract Template): 定义一个模板方法,这个模板方法一般是一个具体方法,给出一个顶级算法骨架,而逻辑骨架的组成步骤在相应的抽象操作中,推迟到子类实现
- 模板方法:定义了算法的骨架,按某种顺序调用其包含的基本方法
- 基本方法:是整个算法中的一个步骤,包括抽象方法和具体方
- 抽象方法:在抽象类中申明,由具体子类实现。
- 具体方法:在抽象类中已经实现,在具体子类中可以继承或重写它
- 具体模板(Concrete Template):实现父类所定义的一个或多个抽象方法,它们是一个顶级算法逻辑的组成步骤
- 抽象模板(Abstract Template): 定义一个模板方法,这个模板方法一般是一个具体方法,给出一个顶级算法骨架,而逻辑骨架的组成步骤在相应的抽象操作中,推迟到子类实现
-
优点
- 扩展性好,对不变的代码进行封装,对可变的进行扩展,符合 开闭原则
-
提高代码复用性 将相同部分的代码放在抽象的父类中,将不同的代码放入不同的子类中
- 通过一个父类调用其子类的操作,通过对子类的具体实现扩展不同的行为,实现了反向控制
-
缺点
- 每一个不同的实现都需要一个子类来实现,导致类的个数增加,会使系统变得复杂
-
模板方法模式和建造者模式区别
两者很大的交集,建造者模式比模板方法模式多了一个指挥类,该类体现的是模板方法模式中抽象类的固定算法的功能,是一个创建对象的固定算法
观察者模式**
-
角色
-
Subject主题:持有多个观察者对象的引用,抽象主题提供了一个接口可以增加和删除观察者对象;有一个观察者数组,并实现增、删及通知操作
-
Observer抽象观察者:为具体观察者定义一个接口,在得到主题的通知时更新自己
-
ConcreteSubject具体主题:将有关状态存入具体观察者对象,在具体主题内部状态改变时,给所有登记过的观察者发出通知
-
ConcreteObserver具体观察者:实现抽象观察者角色所要求的更新接口,以便使本身的状态与主题的状态保持一致
-
- 优点
- 降低了目标与观察者之间的耦合关系,目标与观察者之间建立了一套触发机制
- 观察者和被观察者是抽象耦合的
- 缺点
- 观察者和观察目标之间有循环依赖的话,会触发它们之间进行循环调用,可能导致系统崩溃
- 一个被观察者对象有很多的直接和间接的观察者的话,将所有的观察者都通知到会花费很多时间
责任链模式**
-
责任链设计模式(Chain of Responsibility Pattern)
- 客户端发出一个请求,链上的对象都有机会来处理这一请求,而客户端不需要知道谁是具体的处理对象
- 让多个对象都有机会处理请求,避免请求的发送者和接收者之间的耦合关系,将这个对象连成一条调用链,并沿着这条链传递该请求,直到有一个对象处理它才终止
- 有两个核心行为:一是处理请求,二是将请求传递到下一节点
-
应用场景
- Apache Tomcat 对 Encoding 编码处理的处理,SpringBoot里面的拦截器、过滤器链
- 在请求处理者不明确的情况下向多个对象中的一个提交请求
- 如果有多个对象可以处理同一个请求,但是具体由哪个对象处理是由运行时刻动态决定的,这种对象就可以使用职责链模式
-
角色
- Handler抽象处理者:定义了一个处理请求的接口
- ConcreteHandler具体处理者: 处理所负责的请求,可访问它的后续节点,如果可处理该请求就处理,否则就将该请求转发给它的后续节点
-
优点
- 客户只需要将请求发送到职责链上即可,无须关心请求的处理细节和请求的传递,所以职责链将请求的发送者和请求的处理者 降低了耦合度
- 通过改变链内的调动它们的次序,允许动态地新增或者删除处理类,比较很方便维护
- 增强了系统的可扩展性,可以根据需要增加新的请求处理类,满足开闭原则
- 每个类只需要处理自己该处理的工作,明确各类的责任范围,满足单一职责原则
-
缺点
- 处理都分散到了单独的职责对象中,每个对象功能单一,要把整个流程处理完,需要很多的职责对象,会产生大量的细粒度职责对象
- 不能保证请求一定被接收;
- 如果链路比较长,系统性能将受到一定影响,而且在进行代码调试时不太方便
命令模式
-
备忘录设计模式(Memento Pattern)
- 在不破坏封闭的前提下,捕获一个对象的内部状态,保存对象的某个状态,以便在适当的时候恢复对象,又叫做快照模式,属于行为模式
- 备忘录模式实现的方式需要保证被保存的对象状态不能被对象从外部访问,
-
应用场景
- 玩游戏的时候肯定有存档功能,下一次登录游戏时可以从上次退出的地方继续游戏
- 棋盘类游戏的悔棋、数据库事务回滚
- 需要记录一个对象的内部状态时,为了允许用户取消不确定或者错误的操作,能够恢复到原先的状态
- 提供一个可回滚的操作,如ctrl+z、浏览器回退按钮
角色
-
抽象命令(Command):需要执行的所有命令都在这里声明
-
具体命令(ConcreteCommand):定义一个接收者和行为之间的弱耦合,实现execute()方法,负责调用接收者的相应操作,execute()方法通常叫做执行方法。
-
接受者(Receiver):负责具体实施和执行一个请求,干活的角色,命令传递到这里是应该被执行的,实施和执行请求的方法叫做行动方法
-
请求者(Invoker):负责调用命令对象执行请求,相关的方法叫做行动方法
-
客户端(Client):创建一个具体命令(ConcreteCommand)对象并确定其接收者。
-
优点
- 调用者角色与接收者角色之间没有任何依赖关系,不需要了解到底是哪个接收者执行,降低了系统耦合度
- 扩展性强,新的命令可以很容易添加到系统中去。
-
缺点
- 过多的命令模式会导致某些系统有过多的具体命令类
迭代器模式**
- 角色
- 抽象容器(Aggregate):提供创建具体迭代器角色的接口,一般是接口,包括一个iterator()方法,例如java中的Collection接口,List接口,Set接口等。
- 具体容器角色(ConcreteAggregate):实现抽象容器的具体实现类,比如List接口的有序列表实现ArrayList,List接口的链表实现LinkedList, Set接口的哈希列表的实现HashSet等。
- 抽象迭代器角色(Iterator):负责定义访问和遍历元素的接口,包括几个核心方法,取得下一个元素的方法next(),判断是否遍历结束的方法isDone()(或者叫hasNext()),移除当前对象的方法remove()
- 具体迭代器角色(ConcreteIterator):实现迭代器接口中定义的方法,并要记录遍历中的当前位置,完成集合的迭代
- 优点
- 可以做到不暴露集合的内部结构,又可让外部代码透明地访问集合内部的数据
- 支持以不同的方式遍历一个聚合对象
- 缺点
- 对于比较简单的遍历(像数组或者有序列表),使用迭代器方式遍历较为繁琐
- 迭代器模式在遍历的同时更改迭代器所在的集合结构会导致出现异常
备忘录模式
- 角色
- Originator: 发起者,记录当前的内部状态,并负责创建和恢复备忘录数据,允许访问返回到先前状态所需的所有数据,可以根据需要决定Memento存储自己的哪些内部状态
- Memento: 备忘录,负责存储Originator发起人对象的内部状态,在需要的时候提供发起人需要的内部状态
- Caretaker: 管理者,对备忘录进行管理、保存和提供备忘录,只能将备忘录传递给其他角色
- Originator 和 Memento属性类似
-
优点
- 给用户提供了一种可以恢复状态的机制
- 实现了信息的封装,使得用户不需要关心状态的保存细节
-
缺点
- 消耗更多的资源,而且每一次保存都会消耗一定的内存
状态模式
-
状态设计模式(State Pattern)
- 对象的行为依赖于它的状态(属性),并且可以根据它的状态改变而改变它的相关行为 ,属于行为型模式
- 允许一个对象在其内部状态改变时改变它的行为
- 状态模式是策略模式的孪生兄弟,它们的UML图是一样的,但实际上解决的是不同情况的两种场景问题
- 工作中用的不多,基本策略模式比较多
-
应用场景
- 一个对象的行为取决于它的状态, 并且它必须在运行时刻根据状态改变它的行为
- 代码中包含大量与对象状态有关的条件语句 ,比如一个操作中含有庞大的多分支的条件if else语句,且这些分支依赖于该对象的状态
- 电商订单状态:未支付、已支付、派送中,收货完成等状态,各个状态下处理不同的事情
-
角色
- Context 上下文: 定义了客户程序需要的接口并维护一个具体状态角色的实例,将与状态相关的操作委托给当前的Concrete State对象来处理
- State 抽象状态类: 定义一个接口以封装与Context的一个特定状态相关的行为。
- ConcreteState具体状态类: 实现抽象状态定义的接口。
-
优点
- 只需要改变对象状态即可改变对象的行为
- 可以让多个环境对象共享一个状态对象,从而减少系统中对象的个数
-
缺点
- 状态模式的使用会增加系统类和对象的个数。
- 状态模式的结构与实现都较为复杂,如果使用不当将导致程序结构和代码的混乱
- 状态模式对“开闭原则”的支持并不太好,对于可以切换状态的状态模式,增加新的状态类需要修改那些负责状态转换的源代码