三种类型设计模式
创建型涉及模式
单例模式,工厂模式,建造者模式,原型模式
结构型设计模式
门面模式,适配器模式,代理模式,适配器模式,桥接模式,组合模式,装饰者模式
行为型设计模式
职责链模式,观察者模式,策略模式,模版方法模式,状态模式,命令模式,中介者模式,访问者模式,迭代器模式,备忘录模式
设计原则
一:单一职责原则 (降低类的复杂度,一个类只负责一个职责)
二:李氏代换原则 (所有引用基类的地方必须能够透明的使用其子类对象)
三:迪米特法则 (接口隔离原则,客户端不应该依赖他不需要的接口)
四:依赖倒置原则 (高层模块不应该依赖底层模块,二者都应该依赖抽象;抽象不应该依赖细节;细节应该依赖抽象)总结一句:接口依赖接口是最好的解藕方式)
五:开闭原则 (对于扩展是开放的,对修改是关闭)
六:合成复用原则 (复用时要尽量使用组合/聚合关系(关联关系),少用继承)
类与类之间的几种关系
组合关系 (组合关系在代码上与关联关系表现一致,组合关系有点同生共死的味道,变量创建一般在构造函数中,作为参数存在又具有一定的依赖关系)
聚合关系 (聚合关系在代码上与关联关系表现一致,water对象 作为person类的成员变量)
关联关系(关联关系有单向关联、双向关联、自身关联、多维关联等等)其中后三个可以不加箭头。(从关系的生命周期来看,依赖关系是仅当类的方法被调用时而产生,伴随着方法的结束而结束。关联关系当类实例化的时候产生,当类对象销毁的时候关系结束。相比依赖,关联关系的生存期更长。)
单向关联
双向关联
自身关联
实现 ( 实现指的是一个class类实现interface接口(可以是多个)的功能,实现是类与接口之间最常见的关系。)
依赖关系(依赖在代码中主要体现为类A的某个成员函数的返回值、形参、局部变量或静态方法的调用,则表示类A引用了类B而这种使用关系是具有偶然性的、临时性的、非常弱的。)
策略模式,职责链模式,状态模式 的使用场景,优缺点和异同点
策略模式的uml图
状态模式的uml图
职责链模式的uml图
从uml图上看:
策略模式:
意图:定义一系列的算法,把它们一个个封装起来, 并且使它们可相互替换。
主要解决:在有多种算法相似的情况下,使用 if…else 所带来的复杂和难以维护。
何时使用:一个系统有许多许多类,而区分它们的只是他们直接的行为。
如何解决:将这些算法封装成一个一个的类,任意地替换。
Istrategy作为顶层策略接口,有三个实现类,分别是加,减,乘三个策略类,一个上下文context对象,与策略顶层接口为关联关系(组合关系,在构造函数中),对于客户端client来说,
需要清晰的知道具体有哪些策略,从而灵活的传递。
状态模式:
意图:允许对象在内部状态发生改变时改变它的行为,对象看起来好像修改了它的类。
主要解决:对象的行为依赖于它的状态(属性),并且可以根据它的状态改变而改变它的相关行为。
何时使用:代码中包含大量与对象状态有关的条件语句。
如何解决:将各种具体的状态类抽象出来。
Istate 作为一个抽象类具有模版方法handle 处理不同状态下的不同行为,同时依赖 context上下文对象,同时context也依赖istate,同时拥有handle方法,实际调用的是不同状态处理
不同的行为。构造函数实际初始化最开始的状态。客户端client 依赖 context,可以连续不断的调用,实际在内部维护了一系列的状态关系,通过context.setcurrentstate() 不断改变状态,并且处理相应行为,而客户端不需要对内部的状态是如何关联的,只需要设置响应参数,调用相应的状态类,处理相应的状态行为。
职责链模式:
意图:避免请求发送者与接收者耦合在一起,让多个对象都有可能接收请求,将这些对象连接成一条链,并且沿着这条链传递请求,直到有对象处理它为止。
主要解决:职责链上的处理者负责处理请求,客户只需要将请求发送到职责链上即可,无须关心请求的处理细节和请求的传递,所以职责链将请求的发送者和请求的处理者解耦了。
何时使用:在处理消息的时候以过滤很多道。
如何解决:拦截的类都实现统一接口。
ihandle 作为抽象父类,自己依赖自己,像指针一样指向下一个要处理的对象,像链表一样维护着职责的关系,客户端client,需要很清楚内部的关系,并且进行设置串联,
最终由头节点开始处理,直到有对象接收请求并处理完毕,对于客户端来说,需要维护一个链表一样的关系,如果处理对象的关联比较长的话,设置起来比较复杂,会导致一些
写代码的错误。并且只能处理一个请求,不像状态模式一样可以连续请求,但是相对来说,可以定制处理不同的职责流程,相对来说比较灵活。
策略模式,状态模式,职责链模式的使用场景
策略模式:实现某一个功能可以有多种算法或者策略,我们根据实际情况选择不同的算法或者策略完成该功能,一般就会用if…else或者switch…case语句来选择具体算法,需要增加一种新的排序算法时,需要修改封装算法类的源代码,违背了开闭原则和单一职责原则,将这些算法抽象出来提供一个统一的接口,不同算法有不同的实现类,让算法独立于使用它的客户而独立
对客户端来说:客户端需要了解内部有哪些策略,不需要了解具体实现,灵活替换
策略模式符合哪些设计原则:开闭原则,单一职责原则,从顶层接口与实现类来看,又符合依赖倒转原则,客户端只依赖父类,灵活的指向子类替换。
策略模式的使用场景:
(1)针对同一类型问题的多种处理方式,仅仅是具体行为有差别时 (比如计算器的加减乘除,比如超市的支付方式,微信支付,支付宝支付,现金支付,超市的促销方式)。
(2)需要安全地封装多种同一类型的操作时 (计算,支付,打折)对于动作行为不同策略的封装,
(3)出现同一个抽象类有多个子类,又需要if…else switch-case选择具体子类时
状态模式:状态模式中的行为是由状态来决定的,不同状态下有不同的行为,在代码中体现不同状态的不同行为
public class MorningState extends IState {
@Override
public void hanle(StateContext stateContext) {
if (stateContext.getHour() < 12) {
stateContext.wash();
} else {
stateContext.setState(new NoonState());
stateContext.hanle();
}
}
}
状态模式:和策略结构基本一样,目的本质不同,策略模式的行为是可以相互替代,而策略模式中状态改变,行为也随之改变,比如灯的开关两种状态,比如司机接单状态,接单,到达上车点,开始计费,结束计费,结算等等状态,美团骑手接单等等。
对客户端来说:客户端不需要知道底层具体实现,也不需要具体有什么状态,只需要发送命令就行。内部可以一直处理。
状态模式符合哪些设计原则:单一职责原则。
状态模式使用场景:
(1)一个对象的行为取决于它的状态,并且它必须在运行时根据状态改变它的行为
(2)代码中有大量与状态有关的条件语句
职责链模式:
可以类比于链表,将每一个对象看作是一个对象,每一个对象拥有不同的处理逻辑,将一个请求从链式的首端发出,依次传递给每一个节点对象,直至有对象处理这个请求为止,感觉可以参考链表的定义,就好像链表的遍历
对客户端来说:客户端需要清楚的知道各个职责类是干什么的,并且进行设置,当职责类比价多的情况下,可能会出现一些低级错误,但是设置比较灵活,自动切换在客户端。并且设置一次就只能执行一次。跟管道流一样,通过xml配置文件设置,将各个管道注入,管道中有注入管道(多个注入类),处理管道(多个处理类),输出管道(一个输出结果类),放入ioc容器,随后进行处理。
职责链模式符合哪些设计原则:单一职责,开闭原则
职责链模式的使用场景:
多个对象可以处理同一请求,但具体由哪个对象处理则在运行时动态决定
在请求处理者不确定的情况下向多个对象中的一个一个提交请求
需要动态指定一组对象处理请求
在客户端的代码体现:
public class ResponsibilityClient {
public static void main(String[] args) {
ResponsibilityContext responsibilityContext = new ResponsibilityContext();
responsibilityContext.setLeaveType("2");
AHandle aHandle = new AHandle();
Bhandle bhandle = new Bhandle();
Chandle chandle = new Chandle();
aHandle.setSuccessor(bhandle);
bhandle.setSuccessor(chandle);
aHandle.handle(responsibilityContext);
responsibilityContext.setLeaveType("1");
AHandle aHandle1 = new AHandle();
Bhandle bhandle1 = new Bhandle();
Chandle chandle1= new Chandle();
aHandle1.setSuccessor(bhandle1);
bhandle1.setSuccessor(chandle1);
aHandle.handle(responsibilityContext);
}
}
策略模式,状态模式,职责链模式的优点和缺点
策略模式
1、算法可以自由切换。
2、避免使用多重条件判断。
3、扩展性良好。
1、策略类会增多。
2、所有策略类都需要对外暴露。
状态模式
1、封装了转换规则。
2、枚举可能的状态,在枚举状态之前需要确定状态种类。
3、将所有与某个状态有关的行为放到一个类中,并且可以方便地增加新的状态,只需要改变对象状态即可改变对象的行为。
4、允许状态转换逻辑与状态对象合成一体,而不是某一个巨大的条件语句块。
5、状态类不需要对外暴露
1、状态模式的使用必然会增加系统类和对象的个数。
2、状态模式的结构与实现都较为复杂,如果使用不当将导致程序结构和代码的混乱。
3、状态模式对"开闭原则"的支持并不太好,对于可以切换状态的状态模式,增加新的状态类需要修改那些负责状态转换的源代码,否则无法切换到新增状态,而且修改某个状态类的行为也需修改对应类的源代码。
职责链模式
1、降低耦合度。它将请求的发送者和接收者解耦。
2、简化了对象。使得对象不需要知道链的结构。
3、增强给对象指派职责的灵活性。通过改变链内的成员或者调动它们的次序,允许动态地新增或者删除责任。
4、增加新的请求处理类很方便。
1、不能保证请求一定被接收。
2、系统性能将受到一定影响,而且在进行代码调试时不太方便,可能会造成循环调用。
3、可能不容易观察运行时的特征,有碍于除错。
4、客户端必须清楚各个职责链的具体作用,设置成链的时候职责类比较多的话,可能出现配错的情况。
优点 | 缺点 | |
---|---|---|
状态模式 | 1、封装了转换规则。2、枚举可能的状态,在枚举状态之前需要确定状态种类。3、将所有与某个状态有关的行为放到一个类中,并且可以方便地增加新的状态,只需要改变对象状态即可改变对象的行为。4、允许状态转换逻辑与状态对象合成一体,而不是某一个巨大的条件语句块。5、状态类不需要对外暴露 | 1、状态模式的使用必然会增加系统类和对象的个数。2、状态模式的结构与实现都较为复杂,如果使用不当将导致程序结构和代码的混乱。3、状态模式对"开闭原则"的支持并不太好,对于可以切换状态的状态模式,增加新的状态类需要修改那些负责状态转换的源代码,否则无法切换到新增状态,而且修改某个状态类的行为也需修改对应类的源代码。 |
策略模式 | 1、算法可以自由切换。2、避免使用多重条件判断。3、扩展性良好。 | 1、策略类会增多。2、所有策略类都需要对外暴露。 |
职责链模式 | 1、降低耦合度。它将请求的发送者和接收者解耦。2、简化了对象。使得对象不需要知道链的结构。3、增强给对象指派职责的灵活性。通过改变链内的成员或者调动它们的次序,允许动态地新增或者删除责任。4、增加新的请求处理类很方便。 | 1、不能保证请求一定被接收。2、系统性能将受到一定影响,而且在进行代码调试时不太方便,可能会造成循环调用。3、可能不容易观察运行时的特征,有碍于除错。4、客户端必须清楚各个职责链的具体作用,设置成链的时候职责类比较多的话,可能出现配错的情况。 |
代理模式的uml图 (仔细一看如果不实现同一个接口gift,结构跟策略模式一样)
适配器模式的uml图
装饰者模式的uml图
桥接模式的uml图
代理模式:为其他对象提供一种代理以控制对这个对象的访问。
主要解决:在直接访问对象时带来的问题,比如说:要访问的对象在远程的机器上。在面向对象系统中,有些对象由于某些原因(比如某些操作需要安全控制,或者需要进程外的访问),直接访问会给使用者或者系统结构带来很多麻烦,我们可以在访问此对象时加上一个对此对象的访问层。
何时使用:想在访问一个类时做一些控制。
如何解决:增加中间层。
gift作为公共接口,有送巧克力,送花,送520的行为,两个实现类,一个是小明一号,一个是代理类,代理类同时又具有公共接口的引用(目的是关联被代理类),限制送巧克力的时候小明一号只能送520,送花的时候小明二号只能送巧克力。控制条件,去控制对象行为的访问。客户端只依赖代理类和被代理类,不用管被代理类具有哪些访问权限,访问权限由内部控制
适配器模式:将一个类的接口转换成客户希望的另外一个接口。适配器模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。
主要解决:主要解决在软件系统中,常常要将一些"现存的对象"放到新的环境中,而新环境要求的接口是现对象不能满足的。
何时使用: 1、系统需要使用现有的类,而此类的接口不符合系统的需要。 2、想要建立一个可以重复使用的类,用于与一些彼此之间没有太大关联的一些类,包括一些可能在将来引进的类一起工作,这些源类不一定有一致的接口。 3、通过接口转换,将一个类插入另一个类系中。(比如老虎和飞禽,现在多了一个飞虎,在不增加实体的需求下,增加一个适配器,在里面包容一个虎对象,实现飞的接口。)
如何解决:继承或依赖(推荐)。
target为目标接口,adapterhander为适配器,实现目标接口,同时继承或者组合被适配类(为了拿到被适配类的行为,从而完成适配功能),而客户端只需要拿到适配器和被适配对象,从而完成最终的适配功能的调用,内部具体怎么实现不需要客户端知道。
装饰者模式:动态地给一个对象添加一些额外的职责。就增加功能来说,装饰器模式相比生成子类更为灵活。
主要解决:一般的,我们为了扩展一个类经常使用继承方式实现,由于继承为类引入静态特征,
且随着扩展功能的增多,子类会很膨胀。
何时使用:在不想增加很多子类的情况下扩展类。
如何解决:将具体功能职责划分,同时继承装饰者模式。
componet作为顶层接口,动态的给需要装饰的对象添加动态的功能,其子类Decorator装饰器同时又关联一个component对象,又相当于指针一样,引用自己,跟职责链模式(行为型)感觉又很类似,职责链是在client端进行链表设置,需要客户端对各个职责类的作用有比较清晰的结果,然后对于不同的请求只有一个处理类处理,或者没有处理结果,而装饰者一样要求客户端对于各个装饰者对象的行为有比较清晰的理解,对于最终的处理结果,为所有的装饰处理的结果,内部处理相当于是链式处理。在代码中表现为:
@Override
public void operate() {
super.operate();
this.wearClosees();
}
乔接模式:将抽象部分与实现部分分离,使它们都可以独立的变化。
主要解决:在有多种可能会变化的情况下,用继承会造成类爆炸问题,扩展起来不灵活。
何时使用:实现系统可能有多个角度分类,每一种角度都可能变化。
如何解决:把这种多角度分类分离出来,让它们独立变化,减少它们之间耦合。
sharp形状的抽象类,主要是画的行为,将颜色类color抽象为接口,一个sharp,一个color两个维度,通过组合的方式进行扩展,sharp依赖接口color,从而在颜色维度上灵活扩展
其主要解决类膨胀,继承出现的难以管理的问题,将实现部分(继承)分离抽象成接口或者抽象类。
总结:结构型设计模式,其实都是通过关联或者依赖关系得到相应对象,得到相应对象就相当于得到对象的所有属性或者行为,从而进行对象的行为调用,而拿到对象之后,又可以对
其行为的调用进行控制(条件控制,代理模式),进行增强(适配器模式,装饰者模式),而代理模式,适配器模式,装饰者模式都有接口顶层类。
从设计原则上来看:
符合哪些设计原则 | |
---|---|
代理模式 | 合成复用,依赖倒置 |
适配器模式 | 合成复用,依赖倒置 |
装饰者模式 | 单一职责,开闭,依赖倒置 |
乔接模式 | 单一职责,开闭,依赖倒置 |
优点 | 缺点 | |
---|---|---|
代理模式 | 1,职责清晰 (表现在接口上)2,利于扩展(表现在依赖接口上) | 当进行扩展的时候,顶层接口与子类都需要重新实现新的方法,当行为越来越多的时候,难以维护。 |
适配器模式 | 1、可以让任何两个没有关联的类一起运行;(表现在适配行为上)2、可以在不修改原有代码的基础上来复用现有类,很好地符合 “开闭原则” | 适配器 一:采用了类和接口的“双继承”实现方式,带来了不良的高耦合,所以建议多用组合,少用继承 |
装饰者模式 | 装饰类和被装饰类可以独立发展,不会相互耦合,装饰模式是继承的一个替代模式,装饰模式可以动态扩展一个实现类的功能。 | 多层装饰比较复杂。对于客户端来说,要比较清晰的知道各个装饰类的职能。 |
乔接模式 | : 1、抽象和实现的分离(感觉上述的所有设计模式都涉及抽象与实现相分离)。 2、优秀的扩展能力。 3、实现细节对客户透明。 | 桥接模式的引入会增加系统的理解与设计难度,由于聚合关联关系建立在抽象层,要求开发者针对抽象进行设计与编程。 |
装饰者模式和乔接模式都是解决继承类膨胀问题,通过组合关系来关联类。