现将学习笔记整理出来,供自己和要学设计模式的童鞋参考。
设计模式之间有很多相似之处,即便整体学过以后也要经常复习。
为了复习方便我讲java设计模式一一对应的demo整理的java项目打包,供以后开发中想到需求和某一设计模式挂钩时参考使用。
github地址:https://github.com/xiaoyao880609/design_patterns-demo
UML 常用图标说明:--→:依赖关系【体现为局域变量、方法的形参,或者对静态方法的调用】
→:关联关系【成员变量定义关联实体类属性,可双向,也可单向】
△→:继承关系
△--→:实现关系
◇→:聚合关系(弱拥有关系,生命周一不同步)【成员变量定义实体类集合属性】
◆→:合成关系(强拥有关系,生命周期同步)【成员变量定义实体类集合属性,类初始化同时初始化组合类】
=======================================================================
单一原则:又称单一功能原则,它规定一个类应该只有一个发生变化的原因。
开放-封闭原则:是说软件实体(类,模块,函数等等)应该可以扩展,但不可修改。
依赖倒转原则:抽象不应该依赖细节,细节应该依赖于抽象。(面向接口编程)
里氏代换原则(对依赖倒转的补充):只有衍生类可以替换掉基类,软件单位功能不受影响时,基类才能真正被复用。
迪米特法则-LoD:如果两个类不必彼此直接通讯,那么这两个类就不应该发生直接的相互作用。如果其中一个类需要调用另一个类的某一个方法,可以通过第三者转发这个调用。
合成/聚合复用原则:优先使用对象合成/聚合,而不是继承。
=======================================================================
简单工厂:计算器例子:超类计算器。加,减,乘,除继承超类,创建生成具体计算器,接受(算法符号)来生成对应的算法计算器实例。
![](https://i-blog.csdnimg.cn/blog_migrate/8621d48d5a8e66b812b33b69e3d48803.png)
工厂方法模式:定义一个用于创建工厂对象的接口,让子类决定实例化哪一个类。工厂方法使一个类的实例化延迟到其子类。
创建工厂接口。加,减,乘,除工厂实现工厂接口返回具体加,减,乘,除实例。
客户端实体化具体加,减,乘,除工厂类,通过工厂接口获取具体实例
![](https://i-blog.csdnimg.cn/blog_migrate/5ade93b8c9d8bd2d09012751fcfdab1e.png)
区别:简单工厂模式最大优点在于工厂类包含了必要的逻辑判断,根据客户端的选择条件动态实例化相关类,对于客户端去除了具体产品的依赖。
新加功能时需要修改原有工厂类中的逻辑,不符合开闭原则(对扩展开放,修改关闭)。
工厂方法把简单工厂的内部逻辑判断移到了客户端代码来进行。你想要加功能,本来需要改工厂类的,而现在是修改客户端。
-----------------------------------------------------------------------
抽象工厂模式:提供一个创建一系列相关或相互依赖对象的接口,而无需指定他们具体的类。
抽象工厂接口(有多个实现):包含所有产品创建的抽象方法。
抽象产品接口(有多个实现):定义所有产品抽象方法。
具体工厂类继承抽象工厂方法,依赖具体抽象产品类。
客户端通过多态引用抽象工厂创建产品。
优点:由于抽象工厂在一个应用中只需要初始化时候出现一次,使得改变具体工厂变得简单,只需要改变具体工厂即可使用不同的产品配置。
客户端通过抽象接口操控实例,使具体工厂的实现分离。
策略模式:(算法的变化不影响使用算法的客户。好处:1,:通过继承可以得到公共功能,2:算法之间相互独立,互不干扰,利于单元测试)
策略类(抽象类)定义所有支持算法的公共接口。多个具体算法子类继承策略类实现超类中的抽象方法。
上下文类(负责维护队策略类对象的引用)通过构造方法初始化超类-策略类对象,提供公开方法返回具体策略类算法的结果
客户端:通过注册具体算法实现类来获取上下文类。运行公开算法方法获取结果(因此做到了具体算法的实现与客户端完全分离)
总结:只要在分析过程中不同的时间应用不同的业务规则时候可以考虑使用策略模式!
装饰模式:动态地给对象添加额外的职责,就增加功能来说装饰模式比生成子类更灵活。
(在原有功能基础上不变动原始类扩展来调用原始方法后添加自己独有的功能)
Component-对象抽象接口:定义一个对象抽象接口提供抽象方法(动态添加职责,无需知道Decorator)
ConcreteComponent-具体对象实例:实现对象接口
Decorator-装饰抽象类:继承接口对象构造函数接收ConcreteComponent初始化protected的Component属性(多态),
调用Component的具体方法重写父类抽象方法。
ConcreteDecorator-装饰实体类(多个):继承Decorator-装饰抽象类,重写原始方法,先执行super父类的原始方法后面添加新功能实
总结:装饰模式是为已有功能基础上动态地添加更多功能的一中方式。有效的把类的核心职责和装饰功能区分开,并且可以去除相关类中重复的装饰逻辑。
代理模式:为其他对象提供一种代理以控制对这个对象的访问。
定义要代理的接口。
委托人实现代理接口中的方法。
代理类中关联委托人实体类,用关联的委托人的方法实现代理接口中的方法。
客户端初始化代理类来调用委托人的方法(对于客户端委托人的隐藏的)
原型模式- Prototype:用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。
潜复制-基本类型复制值,引用对象复制引用地址:java中必须要实现Cloneable接口(通知虚拟机可复制)调用super.clone()方法就可以实现原型模式。深复制-基本类型复制值,引用对象复制引用具体实例:被复制对象必须实现Serializable接口,用流写和读取的方式实现深复制。 (把对象写到流里的过程是序列化(Serialization)过程;而把对象从流中读出来的过程则叫反序列化(Deserialization)过程)
ByteArrayOutputStream baos =new ByteArrayOutputStream();
newObjectOutputStream(baos).writeObject(oldObj);
return newObj = newObjectInputStream(newByteArrayInputStream(baos.toByteArray())).readObject();
模板方法模式:定义一个操作中算法的骨架,而将一些步骤延迟到子类中。模板方法使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。
抽象模板(Abstract Template)角色:定义了一个或多个抽象操作,以便让子类实现。这些抽象操作叫做基本操作,它们是一个顶级逻辑的组成步骤。
定义并实现了一个模板方法。这个模板方法一般是一个具体方法,它给出了一个顶级逻辑的骨架,而逻辑的组成步骤在相应的抽象操作中,推迟到子类实现。顶级逻辑也有可能调用一些具体方法。
具体模板(Concrete Template)角色:实现父类所定义的一个或多个抽象方法,它们是一个顶级逻辑的组成步骤。
每一个抽象模板角色都可以有任意多个具体模板角色与之对应,而每一个具体模板角色都可以给出这些抽象方法(也就是顶级逻辑的组成步骤)的不同实现,从而使得顶级逻辑的实现各不相同。
外观模式-门面模式(依赖倒转原则 + 迪米特法则-LoD):把一些复杂的流程封装成一个接口(门面角色)给外部用户更简单的使用
门面角色:外观模式的核心。将被客户端角色调用,内部根据客户角色的需求封装子系统角色功能的组合,从而提供给客户角色调用。
建造者模式:将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。
可以用同一个接口创建复杂的属性不同的产品对象。
创建 抽象的建造者类-Builder,声明抽象的建造详细流程。
具体建造类-ConcreteBuilder继承抽象建造者类,实现具体建造流程。(多个)
指挥者-Director包含具体建造者,构造函数初始化后(传入具体建造类),封装具体建造流程提供客户端调用。
观察者模式:定义了一对多的依赖关系,让多个观察者对象同时监听某一个主题对象,当主题对象状态变化时通知所有观察者更新自己。
主题或者抽象通知者-Subject(抽象类)包含观察者集合和状态标识,提供加减集合方法,和通知方法-Notify(循环集合调用观察者的更新方法),
抽象观察者-Observer为所有具体观察者定义一个接口,在收到主题通知时更新自己。
客户端当修改具体主题的状态标识时调用notify方法批量更新观察者。
❈应用场景:当一个对象改变,需要同时改变其他多个对象。
状态模式-State:主要解决当控制一个对象状态转换的条件表达式过于复杂时,把状态的判断逻辑转移到表示不同状态的一系列类当中,可以把复杂的判断逻辑简化。
抽象状态类-State(接收当前状态-Context):定义一个接口以封装与Context的一个特定状态相关的行为。
具体状态类(多个):实现每一个状态对应的行为,首先判断当前状态是否和具体状态一致,一致执行行为后变更下一个状态,否则先变更Context为下一个状态在执行行为。
Context中维护一个具体状态类来定义当前状态。
适配器模式-Adapter:项目后期维护中,2个类所做的事情相同,只是接口不同时可以通过适配器优化。
定义一个适配器继承目标-Target类,引用被适配者类,通过调用被适配者类中方法重写父类-Target的方法。
Target:目标角色,期待得到的接口.
Adaptee:适配者角色,被适配的接口.
Adapter:适配器角色,将源接口转换成目标接口.
备忘录模式又叫快照模式-Snapshot Pattern:不破坏封装的条件下,将一个对象的状态捕捉(Capture)住,并外部化,存储起来,从而在合适的时候将对象还原到存储时的状态。
发起人-Originator:负责创建Memento,记录当前时刻自身的内部状态,并可使用备忘录恢复内部状态(提供保存备忘录和恢复方法)。
备忘录-Memento:负责存储发起人的内部状态,并防止发起人以外的对象访问(定义私有变量提供初始化,但不提供访问接口)。
管理者:持有备忘录,并提供初始化和获取方法。
客户端:将备忘录信息初始化后保存到管理者中。需要恢复时通过事前定义好的管理者持有的备忘录调用恢复方法。
组合模式:组合多个对象形成树形结构以表示"整体-部分"的结构层次。对单个对象或者组合对象具有一致性。
Component 抽象接口:组合中对象声明接口,用于访问和管理Component子部件。
Leaf实现Component:叶子对象,没有子节点。
Composite实现Component:容器对象,定义有枝节点行为,用来存储子部件。实现Component中声明的子部件相关操作,比如增加和删除。
迭代器模式-Iterator:提供一种方法顺序访问一个聚合对象中的各种元素,而又不暴露该对象的内部表示。(JAVA已经内部封装不需要自己实现该模式)
迭代器角色(Iterator):定义遍历元素所需要的取得下一个元素的方法next(),判断是否遍历结束的方法hasNext()),移出当前对象的方法remove()。
具体迭代器角色(Concrete Iterator):实现迭代器接口中定义的方法,完成集合的迭代。
容器角色(Aggregate):一般是一个接口,提供一个iterator()方法,例如java中的Collection接口,List接口,Set接口等
具体容器角色(ConcreteAggregate):就是抽象容器的具体实现类,比如List接口的有序列表实现ArrayList,List接口的链表实现LinkList,Set接口的哈希列表的实现HashSet等。
单例模式-Singleton(太简单不整理):保证一个类仅有一个实例,并提供全局获取方法。
桥接模式(合成/聚合复用原则):实现系统可能有多角度分类,每一种分类都有可能变化,那么就把这种角度分离出来让他们独立,用合成/聚合的关系关联,减少它们之间的耦合。
抽象接口定义一个分类的超类,子类实现这个超类。(多个软件)
抽象类表示另一个分类的超类与抽象接口是聚合/合成关系,构造函数接口抽象接口的具体子类。调用抽象接口子类方法来实现。
Abstraction(抽象类):用于定义抽象类的接口,它一般是抽象类而不是接口,其中定义了一个Implementor(实现类接口)类型的对象并可以维护该对象,它与Implementor之间具有关联关系,它既可以包含抽象业务方法,也可以包含具体业务方法。
RefinedAbstraction(扩充抽象类):扩充由Abstraction定义的接口,通常情况下它不再是抽象类而是具体类,它实现了在Abstraction中声明的抽象业务方法,在RefinedAbstraction中可以调用在Implementor中定义的业务方法。
Implementor(实现类接口):定义实现类的接口,这个接口不一定要与Abstraction的接口完全一致,事实上这两个接口可以完全不同,一般而言,Implementor接口仅提供基本操作,而Abstraction定义的接口可能会做更多更复杂的操作。Implementor接口对这些基本操作进行了声明,而具体实现交给其子类。通过关联关系,在Abstraction中不仅拥有自己的方法,还可以调用到Implementor中定义的方法,使用关联关系来替代继承关系。
ConcreteImplementor(具体实现类):具体实现Implementor接口,在不同的ConcreteImplementor中提供基本操作的不同实现,在程序运行时,ConcreteImplementor对象将替换其父类对象,提供给抽象类具体的业务操作方法。
命令模式-Command:将一个请求封装一个对象,从而使你可用不同的请求对客户进行参数化,对请求排队或记录请求日志,以及支持可撤回操作。
抽象命令角色,命令超类,声明了一个给所有具体命令类的抽象接口。
具体命令角色,定义一个接收者和行为之间的弱耦合;实现execute()方法,负责调用接收者的相应操作。execute()方法通常叫做执行方法。
请求者角色-Invoker,与Command是聚合关系。负责调用命令对象执行请求,相关的方法叫做行动方法。
接收者角色-Receiver,负责具体实施和执行一个请求。任何一个类都可以成为接收者,实施和执行请求的方法叫做行动方法。
职责链模式-Chain of Responsibility:使多个对象都有机会处理请求,从而避免请求者和接收者之间的耦合关系。将这个对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理它为止。
处理请示的接口-Handler:与它的子类-继任者是聚合关系,提供方法设置继任者。并提供抽象的处理请求方法。
继任者-Successor(Handler子类):包含继任者,处理请求方法中先判断是否有执行权,若没有调用继任者处理请求方法,委托给下一任处理请求。
中介者模式-Mediator(与同事类-Colleague是聚合关系):用一个中介对象来封装一系列对象交互。中介者使各对象不需要显示地相互引用,从而使其耦合松散,而且可以独立地改变它们之间的交互。
抽象中介者-Mediator:定义同事对象到中介者对象的接口。定义一个抽象的发送消息方法,得到同事对象和发送信息。
具体中介者:者实现Mediator接口,它需要知道所有具体同事类(拥有所有同事类的成员变量),并从具体同事接收消息,并且给具体同事对象发出命令。
抽象同事类-Colleague:构造方法,初始化成员变量-中介者对象 。
具体同事类:继承抽象同事类,通过构造函数注册具体中介者对象,使每个具体同事类都与中介建立联系。(每个具体同事类相对独立)
总结:Mediator减少了各个Colleague的耦合,使得可以独立地改变和复用各个Colleague和Mediator类。但由于具体中介类中控制了集中化,因此把交互复杂性变为了中介者的复杂性。
中介者模式一般用于一组对象以定义良好但复杂的方式进行通信的场合,以及想定制一个分布在多个类中的行为,而又不行生成太多子类的场合。
享元模式-Flyweight:采用一个共享来避免大量拥有相同内容对象的开销。最常见、直观的就是内存损耗。享元模式以共享的方式高效的支持大量的细粒度对象。
(目的,使对象共享,减少内存损耗,享元对象能做到共享的关键是区分内蕴状态-Internal State和外蕴状态-External State)
内蕴状态-Internal State:一个内蕴状态是存储在享元对象内部的,并且是不会随环境的改变而有所不同。
外蕴状态-External State:一个外蕴状态是随环境的改变而改变的、不可以共享的。享元对象的外蕴状态必须由客户端保存,并在享元对象被创建之后,在需要使用的时候再传入到享元对象内部。外蕴状态不可以影响享元对象的内蕴状态,它们是相互独立的。
抽象享元角色-Flyweight:给出一个抽象接口,以规定出所有具体享元角色需要实现的方法,参数接收外蕴状态。
具体享元角色-ConcreteFlyweight:实现抽象享元角色规定的方法。如果存在内蕴状态,就负责为内蕴状态提供存储空间。构造函数,内蕴状态作为参数传入。
享元工厂角色:负责创建和管理享元角色。要想达到共享的目的,这个角色的实现是关键!
(定义Map<内蕴状态,享元对象>来维护享元角色对象,先通过内蕴状态从Map中获取,如果没有、通过内蕴创建享元对象放入Map中再返回该对象)
客户端角色:维护对所有享元对象的引用,而且还需要存储对应的外蕴状态。
▽▽▽▽▽▽▽▽▽▽▽▽▽▽复合享元模式▽▽▽▽▽▽▽▽▽▽▽▽▽▽▽▽复合享元角色-ConcreteCompositeFlyweight :复合享元角色所代表的对象是不可以共享的,但是一个复合享元对象可以分解成为多个本身是单纯享元对象的组合。复合享元角色又称作不可共享的享元对象(实现抽象抽象享元角色,定义Map<内蕴状态,享元对象>来维护复合享元角色对象,并且提供公开的添加复合享元对象到Map的方法)
享元工厂角色中额外提供复合享元工厂方法,参数接收内蕴状态集合。遍历集合调用复合享元角色中的添加方法批量将享元工厂方法获取到的享元角色添加到复合享元Map中。
解释器模式-Interpreter:类的行为模式。给定一个语言之后,解释器模式可以定义出其文法的一种表示,并同时提供一个解释器。客户端可以使用这个解释器来解释这个语言中的句子。
抽象表达式-Expression角色:声明一个所有的具体表达式角色都需要实现的抽象接口。这个接口主要是一个interpret()方法,称做解释操作。
终结符表达式-Terminal Expression角色:实现了抽象表达式角色所要求的接口,主要是一个interpret()方法;文法中的每一个终结符都有一个具体终结表达式与之相对应。比如有一个 简单的公式R=R1+R2,在里面R1和R2就是终结符,对应的解析R1和R2的解释器就是终结符表达式。
非终结符表达式-Nonterminal Expression角色:文法中的每一条规则都需要一个具体的非终结符表达式,非终结符表达式一般是文法中的运算符或者其他关键字,比如公式R=R1+R2中, “+"就是非终结符,解析“+”的解释器就是一个非终结符表达式。
环境-Context角色:这个角色的任务一般是用来存放文法中各个终结符所对应的具体值,比如R=R1+R2,我们给R1赋值100,给R2赋值200。这些信息需要存放到环境角色中,很多情况下我们使用Map来充当环境角色就足够了。
访问者模式-Visitor:表示一个作用于某对象结构中的各元素的操作。它使你可以再不改变各元素的类的前提下定义作用于这些元素的新操作。
(数据结构和操作分离,使得在数据结构不变的前提下,很容易添加新操作)
Visitor(抽象访问者-抽象出访问元素的动作):在重载的visit函数中声明访问者可以访问的对象。
Concrete Visitor(具体访问者-实现访问元素的动作):实现一个访问者对于一个具体的元素的操作
Element(抽象元素-定义一个接受访问的操作,其参数为访问者):声明具有访问该类型元素权限的访问者的类型(一般是抽象类型),提供重载的accept函数赋予权限。
Concrete Element(具体元素-实现接受访问操作):实现accept方法,基本上是模板化的visitor.visit(this)
Object Structure(对象结构类-可以枚举元素,并且管理元素):容纳多种类型或许不同,接口或者不同的元素的集合。
有以下情形可以考虑使用访问者模式:
1、一个对象结构包含很多类对象,它们有不同的接口,而你想对这些对象实施一些依赖于其具体类的操作。
2、需要对一个对象结构中的对象进行很多不同的并且不相关的操作,而你想避免让这些操作“污染”这些对象的类。Visitor使得你可以将相关的操作集中起来定义在一个类中。
3、当该对象结构被很多应用共享时,用Visitor模式让每个应用仅包含需要用到的操作。
4、 定义对象结构的类很少改变,但经常需要在此结构上定义新的操作。改变对象结构类需要重定义对所有访问者的接口,这可能需要很大的代价。如果对象结构类经常改变,那么可能还是在这些类中定义这些操作较好。
![](https://i-blog.csdnimg.cn/blog_migrate/6ea34e849b69fc056b829fb889697e5a.png)