一文分清23种设计模式-设计模式及PK小结
Posted 双斜杠少年
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了一文分清23种设计模式-设计模式及PK小结相关的知识,希望对你有一定的参考价值。
前言
设计模式是为了可重用代码(可重用)、让代码更容易被他人理解(可读性)、保证代码可靠性。设计模式不是一种方法和技术,而是一种思想。
面向过程 和 面向对象
面向过程,从名字可以得知重点是过程,而面向对象的重点是对象。面向过程是很直接的思维,一步步的执行,一条道走到底。业务越来越复杂。需要按照不同的功能把一些数据和函数放到不同的文件中。于是人们慢慢地总结、提炼就演变成了面向对象,再根据面向对象的特性提炼出关键点:封装、继承和多态。
面向对象思想就类似我们人类面对复杂场景时候的分析思维:归类、汇总。
区别
面向过程编程这种编程风格是以过程作为基本单元来组织代码的。过程其实就是动作,对应到代码中来就是函数,面向过程中函数和数据是分离的,数据其实就是成员变量。
而面向对象编程的类中数据(属性)和动作是在一起的,这也是两者的一个显著的区别。
面向对象
面向对象编程(Object Oriented Programming,OOP)是一种编程范式或者说编程风格。学术一点讲就是把类或对象作为基本单元来组织代码,并且运用提炼出的:封装、继承和多态来作为代码设计指导。让代码高内聚,低耦合。
OO 符合人类面对复杂事物时思考方式,抽象、建模、分类、归类。
面向对象编程真的就这么好吗?
结论先上:软件设计没有银弹,没有最好的,只有合适的。
复杂的需求关系都是错综复杂的,我们分类、抽象、封装就能得到一个个规范化的模块(类)。因为划分的清晰,每个人只要实现自己负责的模块。然后根据模块之间关系再组装起来即可。
说面向过程是蛋炒饭、面向对象是盖浇饭。蛋炒饭混合在一起,盖浇饭是分层的,如果不要葱,盖浇饭把上面的菜拨了直接换个没葱的菜,蛋炒饭就难搞了,得重新炒一份。
1. OOP三大基本特性
封装
封装,也就是把客观事物封装成抽象的类,并且类可以把自己的属性和方法只让可信的类操作,对不可信的进行信息隐藏。
继承
继承是指这样一种能力,它可以使用现有的类的所有功能,并在无需重新编写原来类的情况下对这些功能进行扩展。
多态
多态指一个类实例的相同方法在不同情形有不同的表现形式。具体来说就是不同实现类对公共接口有不同的实现方式,但这些操作可以通过相同的方式(公共接口)予以调用。
2. 面向对象OOD 7大原则
2.1 单一职责原则
高内聚:自己能做的就不麻烦别人,类之间不要产生太强的耦合。每个类应该只有一个职责。单一原则就是为了把功能细化,细化再细化,每个东西只负责一件事情。
单一职责原则是实现高内聚、低耦合的指导方针,它是最简单但又最难运用的原则。单一职责原则最难划分的就是职责。需要从实际的项目去考虑
2.2 接口隔离的原则
高内聚:接口要尽量小,定制服务:一个接口只服务于一个子模块或业务逻辑。根据经验和常识决定接口的粒度大小
2.3 开放封闭原则
核心思想:一个对象对扩展开放,对修改关闭。其含义是说一个软件实体(类)应该通过扩展来实现变化,而不是通过修改已有的代码来实现变化。
2.4 依赖倒置原则
低耦合:依赖倒置原则 可以减少类间的耦合性
- 要依赖于抽象,不要依赖于具体实现
- 高层模块不应该依赖低层模块,两者都应该依赖其抽象
- 抽象不应该依赖细节,细节应该依赖抽象
依赖倒置更加精简的定义就是**“面向接口编程”**。依赖正置是面向实现编程。因为里氏替换才可以实现依赖倒置;因为依赖倒置才可以实现开闭原则
2.5 里氏代换原则
低耦合:在任何父类出现的地方都可以用它的子类来代替。
2.6 迪米特法则(最少知识)
低耦合:一个对象,应到当对其他对象尽可能少的了解
迪米特法则:最少知识原则,一个类应该对自己需要耦合或调用的类知道得最少。在设计结构上,每一个类都应当尽量降低成员的访问权限。其根本思想就是强调了类之间的松耦合
迪米特法则要求类“羞涩”一点,尽量不要对外公布太多的public方法和非静态的public变量,尽量内敛,多使用private、package-private、protected等访问权限。
2.7 合成聚合复用原则
Composite/Aggregate Reuse Principle(CARP / CRP),合成/聚合复用原则。如果新对象的某些功能在别的已经创建好的对象里面已经实现,那么应当尽量使用别的对象提供的功能,使之成为新对象的一部分,而不要再重新创建。新对象可通过向这些对象的委派达到复用已有功能的效果。简而言之,要尽量使用合成/聚合,而非使用继承。桥接模式即是对这一原则的典型应用。
3. 设计模式
-
创建类:工厂方法模式、建造者模式、抽象工厂模式、单例模式、原型模式(5种)
-
结构类:适配器模式、桥梁(接)模式、组合模式、装饰模式、门面模式、享元模式、代理模式。(7种)
-
行为类:责任链模式、命令模式、解释器模式、迭代器模式、中介者模式、备忘录模式、观察者模式、状态模式、策略模式、模版方法模式、访问者模式(11种)
3.1 创建型模式(5)
创建型模式就是创建对象的,创建对象其本身是一个很耗时的操作。因为我们需要 NEW 去申请内存空间。所以是比较耗时的。所以要找人专门帮我们创建对象。而不是自己造对象。
创建型模式(Creational Pattern)对类的实例化过程进行了抽象,能够将软件模块中对象的创建和对象的使用分离。为了使软件的结构更加清晰,外界对于这些对象只需要知道它们共同的接口,而不清楚其具体的实现细节,使整个系统的设计更加符合单一职责原则。
3.1.1. 工厂方法模式
封装产品变化
模式定义:定义一个用于创建对象的接口,让子类决定实例化哪一个类。工厂方法是一个类的实例化延迟到其子类。
简单工厂模式:
简单工厂模式有唯一的工厂类,工厂类的创建方法根据传入的参数做if-else条件判断,决定最终创建什么样的产品对象。
- 使用反射获取类
- 使用注解获取类
- 优点:
- 1、屏蔽产品的具体实现,调用者只关心产品的接口。
- 2、实现简单
- 缺点:
- 1、增加产品,需要修改工厂类,不符合开放-封闭原则
- 2、工厂类集中了所有实例的创建逻辑,违反了高内聚责任分配原则
https://blog.csdn.net/u012373815/article/details/52454923
工厂方法模式:
工厂方法模式由多个工厂类实现工厂接口,利用多态来创建不同的产品对象,从而避免了冗长的if-else条件判断。
- 优点:
- 1、继承了简单工厂模式的优点
- 2、符合开放-封闭原则(对比简单工厂来说增加品类时不用修改原有系统)
- 缺点:
- 1、增加产品,需要增加新的工厂类,导致系统类的个数成对增加,在一定程度上增加了系统的复杂性。
https://blog.csdn.net/u012373815/article/details/52454923
工厂模式是创建型模式中比较重要的。工厂模式的主要功能就是帮助我们实例化对象的。之所以名字中包含工厂模式四个字,是因为对象的实例化过程是通过工厂实现的,是用工厂代替new操作的。
这样做的好处是封装了对象的实例化细节,尤其是对于实例化较复杂或者对象的生命周期应该集中管理的情况。会给你系统带来更大的可扩展性和尽量少的修改量。
3.1.2. 抽象工厂模式
封装生产线变化,产品族
抽象工厂模式把产品子类进行分组,同组中的不同产品由同一个工厂子类的不同方法负责创建,从而减少了工厂子类的数量。
在以下情况下可以使用抽象工厂模式:
-
一个系统不应当依赖于产品类实例如何被创建、组合和表达的细节,这对于所有类型的工厂模式都是重要的。
-
系统中有多于一个的产品族,而每次只使用其中某一产品族。
-
属于同一个产品族的产品将在一起使用,这一约束必须在系统的设计中体现出来。
-
系统提供一个产品类的库,所有的产品以同样的接口出现,从而使客户端不依赖于具体实现。
- 优点:
- 1、隔离了具体类的生成,使得客户并不需要知道什么被创建
- 2、每次可以通过具体工厂类创建一个产品族中的多个对象,增加或者替换产品族比较方便,增加新的具体工厂和产品族很方便;
- 缺点
- 增加新的产品等级结构很复杂,需要修改抽象工厂和所有的具体工厂类,对“开闭原则”的支持呈现倾斜性。
https://blog.csdn.net/u012373815/article/details/52455243
3.1.3. 建造者模式
防止创建一个对象时丢三落四,如做炒面忘放盐。将一个复杂的对象创建与其表示层分开,使在创建过程中可以有不同的表示。
比如创建不同品牌不同型号的汽车,基本方法相同,实现不同。方法编排不同。
例子:创建宝马车和奔驰车,不同型号的汽车,启动运行顺序不同
使用场景
- 产品相同的方法,不同的执行顺序,产生不同的事件结果,可以使用建造者模式
- 多个零部件都可以装配到一个对象中,不同的组合产生的结果不同,可以使用建造者模式.
- 产品类非常复杂,产品类的调用顺序不同产生不同的效能,可以使用建造者模式.
https://github.com/527515025/design_pattern/tree/master/build
3.1.4. 单例模式
饿汉,懒汉、双重校验、枚举、cas
https://abelyang.blog.csdn.net/article/details/109278110
3.1.5. 原型模式(邮件)
克隆对象,深/浅复制,用于创建重复的对象,同时又能保证性能
原型模式是在内存二进制流的拷贝,要比new 一个对象性能好很多,特别是要在一个循环体内产生大量对象的时候
注意:对象的clone 和 对象内的final 关键字是有冲突的。因为final 类型不允许重赋值,但是clone 就是一个重赋值操作,所以clone和final 同时使用时会报错。
https://blog.csdn.net/u012373815/article/details/75331630
3.2 结构型模式(7)
通过组合类或对象产生更大结构以适应更高层次的逻辑需求
3.2.1. 适配器模式、
包装作用,通过委托方式实现其功能
将一个类的接口转换成客户希望的另外一个接口。适配器模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。
适配器模式修饰非血缘关系类,把一个非本家族(不同的接口或父亲类)的对象伪装成本家族的对象,因此它本质还是非相同接口对象。
http://www.jasongj.com/design_pattern/adapter/
见适配器模式和装饰模式PK
3.2.2. 桥接模式(桥梁模式)
合成聚合复用原则的应用,让多角度的分类独立变化。拆分维度。多个维度(品牌,手自动挡)进行组合。例子:宝马车,奔驰车,自动挡维度拆分
将抽象部分与它的实现部分分离,使它们都可以独立地变化。更容易理解的表述是:实现系统可从多种维度分类,桥接模式将各维度抽象出来,各维度独立变化,之后可通过聚合,将各维度组合起来,减少了各维度间的耦合。
优点:
减少实现类的个数
https://github.com/527515025/design_pattern/tree/master/bridgePattern
http://www.jasongj.com/design_pattern/bridge/
3.2.3. 组合模式
分公司也等于是一个部门
定义:将对象组合成树形结构以表示“部分-整体”的层次结构。组合模式使得用户可以使用一致的方法操作单个对象和组合对象。(操作部门和个人)
优点:
-
高层模块调用简单
-
节点自由增加
缺点:
- 无法限制组合组件中的子组件类型。在需要检测组件类型时,不能依靠编译期的类型约束来实现,必须在运行期间动态检测。
使用场景
- 维护和展示部分-整体关系的场景,如树形菜单、文件和文件夹管理。
- 从一个整体中能够独立出部分模块或功能的场景。
注意:
只要是树形结构,就考虑使用组合模式。
http://www.jasongj.com/design_pattern/composite/
3.2.4. 门面模式(外观模式)
外观模式是一个比较常用的封装模式。要求一个子系统的外部与其内部的通信必须通过一个统一的对象进行。门面模式提供一个高层次的接口,使得子系统更易于使用。例子邮局对象(门面角色)封装邮寄信件的流程。
使用场景:
- 为一个复杂的模块或子系统提供一个供外界访问的接口
- 子系统相对独立——外界对子系统的访问只要黑箱操作即可
- 预防低水平人员带来的风险扩散
注意:
- 一个子系统可以有多个门面
- 门面不参与子系统内的业务逻辑
3.2.5. 享元模式
一片英文文章只需要有26个对象,一盘围棋只需要有两个对象。共享相同或者相似的粒度对象解决对象数量多的问题。
对象池,线程池技术,将共用对象放入 hashmap或 concurrentMap中进行共享,同时伴随的常常有工厂方法生产对象。
池中的对象一旦产生,必然有一个唯一的、可访问的状态标志该对象,池中对象的生命周期由池决定,不由使用者决定。
- **内部状态 **是存储在享元对象内部,一般在构造时确定或通过setter设置,并且不会随环境改变而改变的状态,因此内部状态可以共享。内部状态,在构造时确定。理解为对象的属性。
- 外部状态是随环境改变而改变、不可以共享的状态。外部状态在需要使用时通过客户端传入享元对象。外部状态必须由客户端保存。外部状态,由客户端在调用时传入。理解为对象方法的参数。
优点
- 享元模式的外部状态相对独立,使得对象可以在不同的环境中被复用(共享对象可以适应不同的外部环境)
- 享元模式可共享相同或相似的细粒度对象,从而减少了内存消耗,同时降低了对象创建与垃圾回收的开销
模式缺点
- 外部状态由客户端保存,共享对象读取外部状态的开销可能比较大
- 享元模式要求将内部状态与外部状态分离,这使得程序的逻辑复杂化,同时也增加了状态维护成本
http://www.jasongj.com/design_pattern/flyweight/
3.2.6. 装饰模式
(把类的东西传给别人,别人又用它,这就叫装饰模式。例如IO流)、
装饰模式:动态地给一个对象添加一些额外的职责。装饰模式的本质是*动态组合*。动态是手段,组合是目的
装饰模式,注重对对象功能的扩展,它不关心外界如何调用,只注重对对象功能的加强,装饰后还是对象本身。
PK
见适配器模式和装饰模式PK
见代理模式和装饰模式PK
3.2.7. 代理模式
代理模式:为其他对象提供一种代理以控制对这个对象的访问。
https://blog.csdn.net/u012373815/article/details/52463345
代理模式,注重对对象某一功能的流程把控和辅助。它可以控制对象做某些事,重心是为了借用对象的功能完成某一流程,而非对象功能如何。
普通代理:要求就是客户端只能访问代理角色,而不能访问真实角色。调用者只知代理而不用知道真实的角色是谁,屏蔽了真实角色的变更对高层模块的影响,真实的主题角色想怎么修改就怎么修改,对高层次的模块没有任何的影响,只要你实现了接口所对应的方法,该模式非常适合对扩展性要求较高的场合。当然,在实际的项目中,一般都是通过约定来禁止new一个真实的角色,这也是一个非常好的方案。例子: 律师, 游戏代练 public GamePlayer(IGamePlayer _gamePlayer,String _name)
。
强制代理:强制代理的概念就是要从真实角色查找到代理角色,不允许直接访问真实角色。高层模块只要调用getProxy就可以访问真实角色的所有方法,它根本就不需要产生一个代理出来 例子:明星和经纪人
扩展:https://blog.csdn.net/u012373815/article/details/52464341
代理模式和装饰模式PK
http://www.jasongj.com/design_pattern/proxy_decorator/
3.3 行为型模式(11)
行为型模式,就是对象的行为。对象的每一个操作。
3.3.1. 观察者模式、
定义对象间一种一对多的依赖关系,使得每当一个对象改变状态,则所有依赖于它的对象都会得到通知并被自动更新
JDK util包中提供了Observer 和 Observable 接口供我们使用观察者模式。
优点:
- 观察者和被观察者之间是抽象耦合
- 建立一套触发机制。
缺点:
- 如一个主题被大量观察者注册,则通知所有观察者会花费较高代价
- 如果某些观察者的响应方法被阻塞,整个通知过程即被阻塞,其它观察者不能及时被通知 (异步)
使用场景:
-
关联行为场景。需要注意的是,关联行为是可拆分的,而不是“组合”关系。
-
事件多级触发场景。
-
跨系统的消息交换场景,如消息队列的处理机制。
例如:文件系统、猫鼠游戏,ATM取钱,广播收音机
注意:
- 广播链的问题
在一个观察者模式中最多出现一个对象既是观察者也是被观察者,也就是说消息最多转发一次(传递两次)方便维护和管理。 - 异步处理问题
观察者比较多,而且处理时间比较长,采用异步处理来考虑线程安全和队列的问题。
观察者模式与责任链模式PK
3.3.2. 责任链模式、
模式定义:使多个对象都有机会处理请求,从而避免了请求的发送者和接受者之间的耦合关系。将这些对象连成一条链,并沿着这条链传递该请求,直到有对象处理它为止。
例子:女子三从四德
抽象的处理者实现三个职责:
- 一是定义一个请求的处理方法 handleMessage,唯一对外开放的方法;
- 二是定义一个链的编排方法 setNext,设置下一个处理者;
- 三是定义了具体的请求者必须实现的两个方法:定义自己能够处理的级别getHandlerLevel 和 具体的处理任务 echo。
注意事项:
链中节点数量需要控制,避免出现超长链的情况,一般的做法是在 Handler 中设置一个最大节点数量,在 setNext 方法中判断是否已经是超过其阈值,超过则不允许该链建立,避免无意识地破坏系统性能。
https://github.com/527515025/design_pattern/tree/master/responsibilityChain
3.3.3. 命令模式、
命令模式是一个高内聚的模式,其定义为:将一个请求封装成一个对象,从而让你使用不同的请求把客户端参数化,对请求排队或者纪录请求日志,可以提供命令的撤销和恢复功能。
例子:项目团队接需求例子。
优点
- 类间解耦
调用者与接收者之间没有任何的依赖关系,调用者实现功能时只需调用Command的execute方法。不用了解是那个接收者执行。
- 可扩展性
Command的子类可以非常容易的扩展,而调用者Invoker和高层次的模块Client不产生严重代码耦合。
命令模式结合其他模式会更优秀
命令模式可以结合责任链模式,实现命令组解析任务,结合模版方法模式可以减少Command子类膨胀问题。
命令模式缺点
Command子类膨胀问题。如果有N个命令,就会有N个Command子类。
命令模式使用场景
只要认为是命令的地方就可以采用命令模式,例如模拟DOS命令的时候,触发反馈机制的处理等。
https://blog.csdn.net/u012373815/article/details/78331764
3.3.4. 解释器模式(少用)、
给定一门语言,定义它的文法的一种表示,并定义一个解释器,该解释器使用该表示来解释语言中的句子。
例子:翻译表达式,翻译代码可用大量if和switch实现
优点:
解释器是一个简单愈发分析工具,最显著的优点就是扩展性, 修改语法规则,只需修改姓应的非终结符,表达式就可以了,若扩展语法,则只需要增加非终结符的类就可以了。
缺点
- 类膨胀,每个语法都要产生一个非终结符白哦大事,语法规则表复杂时,会产生大量的文件,不方便维护。
- 解释器模式采用递归调用方法。每个非终结符只关心与自己有关的表达式,每个表达式需要知道最终结果,只能一层一层的剥茧。排查语法错误不方便。
- 效率问题,大量循环和递归,语法复杂时效率低下。
使用场景:
-
重复发生的问题可以使用解释器模式
-
一个简单语法需要解释的场景
注意:
尽量不要在重要的模块中使用解释器模式,否则维护会是一个很大的问题。在项目中可以使用 shell、JRuby、Groovy 等脚本语言来代替解释器模式,弥补 Java 编译型语言的不足。
命令模式和策略模式PK
3.3.5. 迭代器模式、
它提供一种方法访问一个容器对象中各个元素,而又不需暴露该对象的内部细节。是一个没落的模式。
注意
-
** 迭代器模式已经被淘汰,java** 中已经把迭代器运用到各个聚集类(collection)中了,使用 java 自带的迭代器就已经满足我们的需求了。
-
如果是一个java开发就不要自己写迭代器模式了,用jdk 提供的Iterator就能满足要求了
3.3.6. 中介者模式(调停者模式)
每个模块之间不再相互交流,交流都通过中介者进行。每个模块都只负责自己的业务,其他的都丢给中介者处理,降低了模块间的耦合关系
定义:用一个中介对象封装一系列的对象交互,中介者使各对象不需要显示地相互作用,从而使其耦合松散,而且可以独立的改变他们之间的交互。
例子:进销存模块相互依赖通过中介者解决依赖、机场调度中心
优点:
中介者模式的优点就是减少类间的依赖,把原有的一对多的依赖变成了一对一的依赖,同事类只依赖中介者,减少了依赖,当然同时也降低了类间的耦合。
缺点
中介者模式的缺点就是中介者会膨胀的很大,而且逻辑很复杂,原本N个 对象直接的相互依赖关系转为 中介者和同事类的依赖关系,同事类越多,中介者的逻辑就越复杂。
中介者模式的使用场景
中介者模式适合用于多个对象之间紧密耦合的情况,紧密耦合的标准是;在类图中出现了蜘蛛网状的结构。在这种情况下一定要考虑使用中介者模式,这有利于把蜘蛛网梳理为星型结构,使原本复杂混乱的关系变得清晰简单。
当出现如下情况尝试使用中介者模式:
- N个对象之间产生了相互的依赖关系(N>2)
- 多个对象有依赖关系,但是依赖香味尚不确定或者有发生改变的可能,这种情况下一般建议采用中介者模式,降低变更引起的风险扩散。
中介者模式的实际应用
网络拓扑 有三种类型:总线型、环形、星型。
中介者模式也叫做调停者模式,一个对象要额N多个对象交流,就像对象之间的战争,很混乱,这个时需要加入一个中心,所有的类都和中心交流。中心说怎么处理就怎么处理。比如说生活中常见的例子:机场调度中心,mvc 框架
https://blog.csdn.net/u012373815/article/details/76944175
3.3.7、备忘录模式
在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。这样以后就可将该对象恢复到原先保存的状态。(保存原始状态,恢复状态)
就是记录原本状态副本,随着操作当前状态变化后,仍可根据副本恢复到原来状态。
例子:游戏存档实现,男孩子追女孩恢复状态。
角色
-
Originator 发起人角色:记录当前时刻的内部状态,负责定义哪些属于备份范围的状态,负责创建和恢复备忘录数据。
-
Memento 备忘录角色(简单的 javabean)负责存储 Originator 发起人对象的内部状态,在需要的时候提供发起人需要的内部状态。
-
Caretaker 备忘录管理员角色(简单的 javabean)对备忘录进行管理、保存和提供备忘录。
使用场景:
- 需要保存和恢复数据的相关状态场景。
- 提供一个可回滚(rollback)的操作。比如word 的ctrl+z
- 需要监控的副本场景中。
- 数据库连接的事务管理就是用的备忘录模式。
注意:
- 备忘录的生命期,不使用就要过期删除
- 备忘录的性能。不要在频繁建立备份的场景中使用备忘录模式(比如一个 for 循环中)。
扩展:
clone 方式的备忘录与原型模式结合
将原始状态当作以个属性,保存在发起人类中。创建备份时,通过调用clone方法,创建备份。恢复时,将属性对象内容恢复即可。
- 注意浅拷贝和深拷贝问题。
- 使用clone 方式的备忘录模式,适合简单的场景和比较单一的场景,不要与其他的对象产生严重的耦合关系。
多状态备忘录(多个状态)
通过一个beanUtils 对象,把发起人的所有属性值转换到HashMap 中,方便备忘录角色存储。restoreProp 方法则是把HashMap 中的值返回到发起人角色中。
3.3.8、状态模式、
模式定义:当一个对象内在状态改变时允许其改变行为,这个对象看起来像改变了其类。状态模式的核心是封装,状态的变更,引起了行为的变更。从外部看就好像这个对象对应的类发生了改变一样。
例子:电梯的状态转变
状态模式的核心是封装,将状态以及状态转换逻辑封装到类的内部来实现,也很好的体现了“开闭原则”和“单一职责原则”。每一个状态都是一个子类,不管是修改还是增加状态,只需要修改或者增加一个子类即可。在我们的应用场景中,状态数量以及状态转换远比上述例子复杂,通过“状态模式”避免了大量的if-else代码,让我们的逻辑变得更加清晰。同时由于状态模式的良好的封装性以及遵循的设计原则,让我们在复杂的业务场景中,能够游刃有余地管理各个状态。
环境角色有两个不成文的规定:
- 将状态对象声明为静态变量,有几个状态对象就声明几个静态变量。
- 环境角色具有状态抽象定义角色的所有行为,具体执行用委托方式
优点:
- 结构清晰
- 遵循设计原则,封装性好
缺点:
- 子类太多,状态多,类膨胀
使用场景:
-
行为随状态改变而改变的场景这也是状态模式的根本出发点,例如权限设计,人员的状态不同即使执行相同的行为结果也会不同,在这种情况下需要考虑使用状态模式。
-
条件、分支判断语句的替代者
注意:
状态模式适用于当某个对象在它的状态发生改变时,它的行为也随着发生比较大的变化,也就是说在行为受状态约束的情况下可以使用状态模式,而且使用时对象的状态最好不要超过 5 个。
https://github.com/527515025/design_pattern/tree/master/state
状态模式和策略模式PK
3.3.9、策略模式、
定义:定义一系列算法,将每个算法都封装起来,并且它们可以互换。策略模式是一种对象行为模式。
策略模式采用了面向对象的继承和多态。对于流程保持封闭,对于可能扩展的策略规则进行开放
和工厂方法很像,比工厂方法多隐藏了一层,即工厂方法产生出的不同对象也被隐藏了,直接拿到计算结果。
一般策略模式配合工厂模式使用。
优点:
- 算法自由切换
- 扩展性好
缺点:
- 每个策略都是一个类,策数量类多,复用性小
- 所有策略都需要对外暴露
使用场景:
- 多个类只有在算法或行为上稍有不同的场景。
- 算法需要自由切换的场景。
- 需要屏蔽算法规则的场景。
注意事项:具体策略数量超过 4 个,则需要考虑使用混合模式
策略模式扩展:策略枚举
定义:
-
它是一个枚举。
-
它是一个浓缩了的策略模式的枚举。
注意:
受枚举类型的限制,每个枚举项都是 public、final、static 的,扩展性受到了一定的约束,因此在系统开发中,策略枚举一般担当不经常发生变化的角色。
3.3.10、模版方法模式
定义一个操作中的算法的框架,而将一些步骤延迟到子类中。使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。
- 基本方法:也叫做基本操作,是由子类实现的方法,并且在模板方法被调用。
- 模板方法:可以有一个或几个,一般是一个具体方法,也就是一个框架,实现对基本方法的调度,完成固定的逻辑。
注意 为了防止恶意的操作,一般模板方法都加上final关键字,不允许被覆写。实现基本方法,使用模版方法的调度逻辑。
例子: 悍马车建造并run,不同的悍马车型号,相同的模版方法
优点
-
封装不变部分,扩展可变部分
把认为是不变部分的算法封装到父类实现,而可变部分的则可以通过继承来继续扩展。在悍马模型例子中,是不是就非常容易扩展?例如增加一个H3型号的悍马模型,很容易呀,增加一个子类,实现父类的基本方法就可以了。 -
提取公共部分代码,便于维护
我们例子中刚刚走过的弯路就是最好的证明,如果我们不抽取到父类中,任由这种散乱的代码发生,想想后果是什么样子?维护人员为了修正一个缺陷,需要到处查找类似的代码! -
行为由父类控制,子类实现
基本方法是由子类实现的,因此子类可以通过扩展的方式增加相应的功能,符合开闭原则。
https://blog.csdn.net/u012373815/article/details/52464460
https://github.com/527515025/design_pattern/tree/master/template
3.3.11、访问者模式
定义:封装一些作用于某种数据结构中的各元素的操作,它可以在不改变数据结构的前提下定义作用于这些元素的新的操作。
双分派
充分利用双分派技术,实现处理与数据结构分离。例如应对男女对不同情景反应不同,人只分男女两类是固定不变的,然后为每种行为提供男女两种实现,然后男的永远调男的的实现,女的调女的的实现,调用时还把自己传进去,即双分派
例子:打印员工的工作和工资报表给不同角色看。
优点:
- 符合单一指责原则
- 优秀的扩展性
- 灵活性高
缺点:
- 具体元素对访问者公布细节,违反迪米特法则
- 具体元素变更增减比较困难
- 违背了依赖倒置原则
使用场景:
- 一个对象结构包含很多类对象,它们有不同的接口,而你想对这些对象实施一些依赖于其具体类的操作,也就说是用迭代器模式已经不能胜任的情景。
- 需要对一个对象结构中的对象进行很多不同并且不相关的操作,而你想避免让这些操作“污染”这些对象的类。
https://github.com/527515025/design_pattern/tree/master/visitor
4.设计模式大PK
4.1. 创建类
4.1.1 工厂模式VS 建造者模式
功能:建造者最主要的功能是基本方法的调用顺序编排,通俗的说就是零件的装配,装配顺序不同,产生的对象不同。而工厂方法重点是创建,创建零件(产品)是主要职责,组装顺序不关心。
4.1.2 抽象工厂模式VS建造者模式
关注点:抽象工厂比建造者模式的尺度大,关注产品的整体,而建造者模式关注构建过程。如果希望屏蔽对象的创建过程只提供一个封装良好的对象,则使用抽象工厂方法模式。如果通过零件装配顺序不同产生新的对象,则使用建造者模式。
4.1.3 工厂模式VS 简单工厂模式VS抽象工厂模式
简单工厂模式:
简单工厂模式有唯一的工厂类,工厂类的创建方法根据传入的参数做if-else条件判断,决定最终创建什么样的产品对象。
- 使用反射获取类
- 使用注解获取类
工厂方法模式:
工厂方法模式由多个工厂类实现工厂接口,利用多态来创建不同的产品对象,从而避免了冗长的if-else条件判断。
抽象工厂模式:
封装生产线变化
抽象工厂模式把产品子类进行分组,同组中的不同产品由同一个工厂子类的不同方法负责创建,从而减少了工厂子类的数量。
4.2. 结构类
4.2.1 代理模式和装饰模式PK
代理模式,注重访问权限和对对象某一功能的流程把控和辅助。它可以控制对象做某些事,重心是为了借用对象的功能完成某一流程,而非对象功能如何。
装饰模式,注重对对象功能的扩展,它不关心外界如何调用,只注重对对象功能的加强,装饰后还是对象本身。
代理模式:是把当前的行为委托给其他对象执行,代理负责接口限定,是否可以调用真实角色,它不对被代理对象的功能做任何处理,保证原汁原味的调用。
装饰模式:是在要保证接口不变的情况下,加强类的功能。保证被装饰得类功能比原始对象更丰富。不做准入条件判断和准入参数过滤。
装饰模式主要是强调对类中代码的拓展,而代理模式则偏向于委托类的访问限制
总结:
- 从语意上讲,代理模式是为控制对被代理对象的访问,而装饰模式是为了增加被装饰对象的功能
- 代理类所能代理的类完全由代理类确定(编译时确定),装饰类装饰的对象需要根据实际使用时客户端的组合(运行时确定)来确定
- 被代理对象由代理对象创建,客户端甚至不需要知道被代理类的存在;被装饰对象由客户端创建并传给装饰对象
4.2.2 适配器模式和装饰模式PK
-
施与对象不同
装饰模式和适配器模式都是包装作用,都是通过委托方式实现其功能。不同的是,装饰模式包装的是自己的兄弟类,隶属于同一个家族,而适配器模式修饰的是非血缘关系的类,把一个非本家族的对象伪装成本家族的对象。注意是伪装。
-
意图不同
装饰模式的意图是加强被装饰对象功能。适配器模式的意图是转化,两个不同的对象之间的转化
-
使用场景不同
装饰模式任何时候都可以使用,只要是想加强类的功能就可以。适配器模式更多的时候是一个补救措施,在一个成熟的系统中,做一个紧急处理手段使用。
-
扩展性不同
装饰模式容易扩展。可随意增加增加装饰。
适配器模式,相当于两个对象间增加一个桥梁,增加容易,去掉较难,需要从系统的角度考虑能否去掉这个桥梁。
4.3. 行为类
4.3.1命令模式和策略模式PK
- 目的:策略模式的意图是封装算法,让这些算法相互独立,并且可以相互替换,让行为的变化独立于拥有行为的客户。命令模式的目的是对动作解耦,把一个动作的执行分为执行对象(接收者角色)、执行行为(命令角色),让两者相互独立互不影响。
- 关注点不同:策略关注的是算法替换问题,是算法的完整性、封装性。命令模式关注的是解耦问题,把请求封装成一个个的命令,由接收者执行。
- 使用场景不同:策略模式用于算法要求变换的场景。命令模式用于解耦两个有紧耦合关系的对象场合,或多命令多撤销场景。
- 抽象策略和抽象对象不同。策略模式是抽象行为算法。命令模式是抽象的接收者,不同的接收者行为不同。例子:餐厅做饭,策略模式抽象的菜品,厨师可以做不同的菜品。命令模式抽象的是厨师,不同的厨师只能做不同的菜。
4.3.2 状态模式和策略模式PK
- 目标不同:策略模式的意图是封装不同算法,算法之间没有交互,达到算法自由切换的目的。状态模式封装的是不同的状态,达到状态切换行为随之发生改变的目的。
- 环境角色职责不同:两者都有context环境角色类。策略模式通过Context产生唯一一个策略作用于代码中,起委托作用,负责算法替换。而状态模式则是通过context组织多个状态流转、状态的登记、变化、协作功能。 形成一个状态转换图来实现业务逻辑。
- 解决问题重点不同:策略模式旨在解决内部算法如何改变的问题,保证算法可以自由切换;状态模式旨在解决内在状态改变而引起行为改变的问题,出发点是事物的状态,封装状态,暴露行为,一个对象的状态改变,从外界看好像是行为改变。
- 应用场景:策略只是算法的封装,是一系列平行的,可相互替换的算法封装结果。所以策略的应用场景:算法(一维)必须是平行的。状态模式则要求有一系列的状态发生变化的场景,要求有状态且有行为的场景,也就是对象必须是二维(状态+行为)描述才采用状态模式
- 复杂度不同:策略模式比较简单,结构简单、扩展容易。状态模式比较复杂,有很多的状态改变和行为交织,涉及面多,虽然容易扩展,但是一般不会大规模扩展。
4.3.3 观察者模式与责任链模式PK
- 链中消息对象不同:观察者模式广播链在传播过程中消息是随时更改的,由相邻的两个节点协商的消息结构。责任链模式在消息传播过程中基本保持消息不可变(结构不变),如果改变,也只是在原有的消息上进行修正。
- 上下级节点关系不同:责任链上下节点没有关系,都是接收同样的对象,所有对象都是从链首传递过来的,上一个节点是什么没关系,自己只按照自己的逻辑处理就行。观察者触发链不同,上下级关系很亲密,链中的任意两个相邻节点都是一个牢固的独立团体。
- 消息的分销渠道不同:责任链模式消息是链首到链尾,单一的固定的方向。观察者触发链不同消息的传递是不固定的,可以是广播也可以是跳跃,取决于处理消息的逻辑。
4.4. 跨区PK
4.4.1策略VS桥梁(桥接)
策略模式是建立一套可以自由切换的算法。桥接模式是在不破坏封装的前提下,解决抽象和实现都可以独立扩展的模式(比如车的品牌和手自动档两个抽象的扩展)。
4.4.2门面VS中介者
门面模式是为负责的子系统提供一个统一的访问界面。中介者是使用一个终结对象封装一系列同事对象的交互行为,各对象都只和中介对象交互,从而使其耦合松散。
4.4.3建造者VS模版
-
建造者最主要的功能是基本方法的调用顺序编排,通俗的说就是零件的装配,装配顺序不同,产生的对象不同。
-
模版方法模式定义一个操作中的算法的框架,而将一些步骤延迟到子类中。使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。
基本方法:也叫做基本操作,延迟由子类实现的方法,并且在模板方法被调用。
模板方法:可以有一个或几个,一般是一个具体方法,也就是一个框架,实现对基本方法的调度,完成固定的逻辑。
注意: 为了防止恶意的操作,一般模板方法都加上final关键字,不允许被覆写。实现基本方法,使用模版方法的调度逻辑。
4.4.4 5个包装模式小析
代理模式:
代理模式主要用在不希望展示一个对象内部细节的场景,控制对象属性方法的访问权限。通过代理类实现功能封装。
装饰模式
装饰模式可以理解为一个特殊的代理模式,它的主要关注点在于,对象功能的增强,添加额外的职责。就扩展性来言比实现子类更加灵活。
适配器模式
适配器模式主要意图是接口转换,将一个没有血缘关系的接口转化包装为本家族的类。通过接口转化屏蔽外界接口,以免外界接口深入系统内部。从而提高系统的稳定性和可靠性。
桥梁模式(桥接模式)
桥梁模式是在抽象层产生耦合,解决自行扩展问题,使两个有耦合关系的对象互相不影响的扩展。可以减少子类膨胀问题。
门面模式
门面模式是一个粗力度的封装,提供一个方便访问子系统的接口,不具有业务逻辑,只是一个访问复杂系统的快速通道。没有它系统照样运行,有了它只是更方便访问而已。
5 设计模式混编
5.1 命令模式+责任链模式
通过命令模式进行命令的分发,把适当的命令分发到指定的责任链上进行处理。
5.2 工厂方法+策略模式
通过工厂模式生产映射策略对象(避免策略模式对外暴露具体的策略问题,由工厂模式统一管理),然后执行策略。
例子 ATM 取钱
5.3 观察者模式+中介者模式
观察者模式发现变更,通知到中介者,中介者进行处理,通知相应的对象。避免观察者和触发者过多的网状交流。
例子:桌面应用的变更多触发例子。
本文主要参考《设计模式之蝉第二版》
以上是关于一文分清23种设计模式-设计模式及PK小结的主要内容,如果未能解决你的问题,请参考以下文章