常用设计模式

设计模式是程序员写出结构优雅代码的必备基础,以下是研发中常见的一些设计模式。

适配器与外观模式

适配器模式定义:将一个类的接口转化成客户期望的另一个接口,使得原本接口不兼容的类可以合作。
适配器使用过程:
(1)客户通过调用适配器的方法对适配器发出请求
(2)适配器使用被适配者接口把请求转化成被适配者的一个或多个调用接口
(3)客户接受调用的结果,并未察觉这是适配器起转换作用

适配器可以适配多个类,同时若一个系统存在新接口的同时也存在旧的接口,并且新旧接口都在使用,那么适配器可以同时实现2个接口,则适配器既可以当新的接口也可以当旧的接口使用。适配器结构图如下所示:
img
适配器模式分为对象适配器和类适配器2种模式:对象适配器使用了组合的方式,委托给被调用者来实现,同时对象适配器可以适配类的所有子类;类适配器使用了多继承的方式,适配器继承了适配者和被适配者,所以它不需要实现被适配者,必要时候也可以覆盖被适配者的行为,但在java中不支持多继承!

适配器 VS 装饰者 VS 外观模式
装饰者 :与责任相关,不改变原有的接口,加入新的责任和功能,可以包装多次
适配器 :将接口转化为另一种接口,无需改变原有的代码
外观 :让接口更简单,对子系统进行了封装

外观模式

外观模式定义:提供了一个统一的接口,用来访问子系统的一群接口。
外观定义了一个高层接口,让子系统更容易使用而不会修改它,当然你也可以直接使用子系统。外观模式使得客户实现从子系统中解耦,当以后系统升级改变时,只需要修改外观代码而不用修改客户代码。其中重要的一点是:适配器和外观模式都可以使用多个类,差别在于适配器改变原有的接口来满足需求,而外观模式简化了接口来访问子系统。

最小知识原则:只和你的密友谈话,注意和你交互的类有哪些,不要将过多的类耦合在一起。
要求:对于一个对象,最好应该只调用以下范围的方法
(1)该对象本身
(2)作为方法的参数传递进来的对象
(3)对象的任何组件
(4)方法内创建和实例化的对象

装饰者模式

解决的问题:开放-关闭原则(类应该对扩展开放,对修改关闭)
装饰者模式定义:动态的将责任附加到对象上,若要扩展功能,装饰者提供了比继承更有弹性的替代方案
装饰者模式意味着一群装饰者类,这些类用来包装具体组件,装饰者反映出被装饰组件的类型,事实上他们具有相同的类型(实现统一接口或集成同一超类),同时装饰者可以在被装饰者行为前后加上自己的行为来达到特定的目的,装饰者一般对组件的客户是透明的,具体的结构如下图所示:
img
其中Compoment是基本组件,ConcreteComponent是具体需要装饰的对象,Decorator是装饰者抽象类,ConcrereDecoratorA/B是具体的装饰者类,用来包装ConcreteComponent,他们都继承与Component。

以星巴克咖啡为例:有多种饮料和调料,如何来描述和计算价格,若采用继承机构将会出现大量的类并且难于维护(计算价格特别麻烦),采用装饰者模式的结构如下图所示:
img
同时jdk中的IO也是典型的装饰者模式,如下图所示:
img
装饰者模式缺点:虽然比继承更容易扩展,但是会导致设计中出现许多的小对象,如果过度使用会让程序较为复杂,难于理解。

工厂模式

工厂模式定义:定义了一个创建对象的接口,但由子类决定要实例化的类是哪一个。即工厂方法让类把实例化推迟到子类。
另一种看法:工厂方法将生产知识封装进各个创建者,创建者和产品是一种平行的类层级,以生产pizza为例,层次结构如下图所示:
img
抽象工厂模式定义:提供一个接口,用于创建相关或依赖对象的家族,而不需要明确指定类。
该模式使得客户不需要知道具体的产品是什么,客户从具体的产品中被解耦。
以pizza为例:如下图所示
img
工厂模式 vs 抽象工厂模式
工厂模式:利用继承方式,通过子类来实例化具体的产品
抽象工厂模式:利用组合方式,创建一个创建产品的抽象类,该类型子类定义了产品被产生的方法,然后实例化该工厂来产生具体的产品

观察者模式

OO原则
(1)封装变化
(2)多用组合(几个对象协同工作),少用继承
(3)针对接口编程,不针对实现编程
(4)为交互对象之间的松耦合设计不断努力

观察者模式定义:定义对象之间的一对多耦合,这样以来,当一个对象的状态变化时,它的所有依赖者都能收到通知并自动更新
该模式主要包含2部分:主题Subject、观察者Observer,主题负责提供对象的状态,增加删除观察者,并把对象的变化状态通知给观察者,具体的模式信息如下图所示:
img
img
以气象发布战为力例子,模式设计图如上所示:
weatherData实现Subject接口,实现register、remove(list持有对观察者的引用)、notify(调用所有观察者update方法通知他们)三个主要方法,CurrentCondition实现Observer借口实现update方法。
jdk中java.util.Observable java.util.Observer分别充当了主题和观察者角色,所以在实现该模式时无需自己实现细节,可以利用jdk来实现自己的观察者模式。

JDK观察者模式缺点:
(1)Observable是一个抽象类而不是借口,应该多使用接口而非抽象类,不利于扩展
(2)setChanged是一个protected方法,只能继承observable,否则不能通知观察者,不够灵活
可以借鉴jdk思想实现自己的通知方式,灵活使用观察者模式。

代理模式

代理模式的结构如下图:
img
代理类Proxy和被代理类RealSubject都实现了同一接口,proxy负责realSubject的访问和控制。

JAVA中的代理RMI
img
RMI使用步骤:
(1)制作远程接口:扩展java.rmi.Remote所有方法都抛出异常RemoteException,确定变量和返回值都是可序列化的
(2)制作远程实现:实现远程接口,构造一个不带参数构造函数并声明RemoteException,利用rmi registry注册此服务
(3)利用rmic产生stub、skeleton:在远程实现类(非接口)执行rmic,执行rmiregistry,启动服务(注册对象)
(4)启动rmi registry(rmiregistry,使得客户可以查到代理的位置)
(5)开始远程服务

注意点:
(1)在启用远程服务前必须先启用rmiregistry
(2)变量和返回值都必须是可序列化的,否则运行时会出错
(3)客户在lookup代理时,必须有stub类(由rmic产生),否则stub对象无法被反序列化,客户端需要调用远程对象返回的序列化对象!
(4)服务端游stub与skeleton类,需要stub是因为stub是真正服务对象的替身,当对象被绑定时,真正绑定的对象是服务端的stub对象!

JDK中的动态代理 Proxy与InvocationHandler
InvocationHandler起辅助作用,将proxy产生的代理类请求交给真正对象去处理,具体的结构图如下所示:
img

迭代器与组合模式

迭代器模式定义:提供一种方法顺序访问一个聚合对象中的各个元素,而又不暴露其内部的表示!
迭代器将元素游走遍历的职责交给迭代器,而不是聚合对象,这样简化了聚合接口的实现,让责任各得其所。
迭代器结构图如下所示:
img
单一职责:一个类应该只有一个引起变化的原因;尽量让每个类保持单一责任

组合模式定义:允许你将对象组合成树形结构来表现“整体/部分”层次结构。组合能让客户以一致的方式处理个别对象以及对象组合(处理时忽略对象组合和个别对象之间的差异)。

组合模式往往需要一个抽象类,类中的方法对于叶子结点和组合节点不同,有些方法适用于叶子结点,而有些节点适用于组合节点,所以在处理时根据情况覆盖抽象类方法,有时候还需要处理异常情况,否则需要进行类型判断对用户不再透明!

单例模式

单例模式定义:
只能创建一个对象,保证线程安全,提供全局访问点
注意点:线程安全、效率、私有构造函数、静态对象

方法:急切加载 、double-check(voliate)、枚举、静态内部类(静态私有内部类,final私有staic变量)
推荐后两者

状态模式

定义:允许对象在内部状态改变时改变它的行为,对象看起来好像修改了它的类!
使用方法:
(1)定义一个State接口,每一个接口都有一个对应的方法
(2)为系统中每一个状态实现State接口实现状态类,这些状态类负责相应状态下系统的行为
(3)将动作委托到状态类

系统状态可以被多个Context共享,将状态设计为静态变量
状态模式结构图如下所示:
img

模版方法模式

定义:在一个方法中定义一个算法的骨架,而将一些步骤延迟到子类中。模版方法使得之类可以在不改变算法结构的情况下,重新定义算法中的某些步骤,即由子类实现算法的部分实现!
JDK中Arrays.sort方法中comparable将compareTo方法的实现交由子类实现,是一种模版方法!
一般情况下,我们将抽象类中的算法的骨架设计为final类型的方法防止子类去修改,另外,可以在抽象类中将可选方法设计为hook(钩子)方法,子类可以选择覆盖来达到某种目的。

系统结构图:
img
好莱坞原则:别调用我们,我们会调用你!
img
好莱坞原则:高层组件对底层组件的原则:别调用我,我会调用你

模版方法模式 VS 策略模式
都是封装算法,模版方法使用了继承,由子类去实现算法某一部分细节;策略模式使用了组合,用委托决定采用哪一个行为

命令模式

命令模式定义:将请求封装为命令对象,以便来使用不同的请求,队列或日志来参数化其他对象。命令模式也支持撤销操作。
命令对象在特定接受者上绑定一组动作来封装一个请求,命令是通过将动作和接受者绑定在一起做到的,命令对象通常只暴露一个execute方法,该方法被调用时,接受者就会进行相关的操作。从表面看,调用者不知道哪个接受者进行了哪些动作,从而通过命令对象将调用者和接受者进行了解耦。

命令模式的用途:命令可以讲运算块打包(一组动作和一个接受者),然后传来传去。命令对象甚至可以在不同的线程中被调用,所以它可以用来:(1)日程安排(2)线程池(3)工作队列

命令模式将发出请求的对象和执行请求的对象解耦,这两者之间是通过命令对象进行沟通的,命令对象封装了接受者和一组动作,命令支持撤销,通过宏命令(多组命令)可以对简单命令进行扩展。命令用于线程队列、日志系统、事物系统等。

代码

Java代码 : https://github.com/lifeloner/designPattern

谢谢大佬的打赏!