设计模式,及软件设计中的“套路”。每一个模式描述了一个在我们周围不断重复发生的问题,以及该问题解决方案的核心,这样,你就能一次又一次的使用该方案而不必做重复的劳动。设计模式大约有20多种,它们使人们可以更加简单方便的复用成功的设计和体系结构,提高系统维护的有效性。与设计模式密切相关的是6大设计原则,那么就从这些设计原则开始设计模式重温之旅吧。(ps:内容有点小多)
一、6大设计模式
1、单一职责原则
- 核心思想:应该有且仅有一个原因引起类的变更
- 问题描述:假如有类Class1完成职责T1,T2,当职责T1或T2有变更需要修改时,有可能影响到该类的另外一个职责正常工作。
- 好处:类的复杂度降低、可读性提高、可维护性提高、扩展性提高、降低了变更引起的风险。
- 需注意: 单一职责原则提出了一个编写程序的标准,用“职责”或“变化原因”来衡量接口或类设计得是否优良,但是“职责”和“变化原因”都是不可以度量的,因项目和环境而异。
单一职责就是让类(或方法)的功能单一,做到术业有且专攻,是不是有点UNIX设计思想的感觉?
2、里斯替换原则
- 核心思想:在使用基类的的地方可以任意使用其子类,能保证子类完美替换基类。
- 通俗来讲:只要父类能出现的地方子类就能出现。反之,父类则未必能胜任。
- 好处:增强程序的健壮性,即使增加了子类,原有的子类还可以继续运行。
- 需注意:如果子类不能完整地实现父类的方法,或者父类的某些方法在子类中已经发生“畸变”,则建议断开父子继承关系 采用依赖、聚合、组合等关系代替继承。
3、依赖倒置原则
- 核心思想:高层模块不应该依赖底层模块,二者都该依赖其抽象;抽象不应该依赖细节;细节应该依赖抽象;
- 说明:高层模块就是调用端,低层模块就是具体实现类。抽象就是指接口或抽象类。细节就是实现类。
- 通俗来讲:依赖倒置原则的本质就是通过抽象(接口或抽象类)使个各类或模块的实现彼此独立,互不影响,实现模块间的松耦合。
- 问题描述:类A直接依赖类B,假如要将类A改为依赖类C,则必须通过修改类A的代码来达成。这种场景下,类A一般是高层模块,负责复杂的业务逻辑;类B和类C是低层模块,负责基本的原子操作;假如修改类A,会给程序带来不必要的风险。
- 解决方案:将类A修改为依赖接口interface,类B和类C各自实现接口interface,类A通过接口interface间接与类B或者类C发生联系,则会大大降低修改类A的几率。
- 好处:依赖倒置的好处在小型项目中很难体现出来。但在大中型项目中可以减少需求变化引起的工作量。使并行开发更友好。
4、接口隔离原则
- 核心思想:类间的依赖关系应该建立在最小的接口上
- 通俗来讲:建立单一接口,不要建立庞大臃肿的接口,尽量细化接口,接口中的方法尽量少。也就是说,我们要为各个类建立专用的接口,而不要试图去建立一个很庞大的接口供所有依赖它的类去调用。
- 问题描述:类A通过接口interface依赖类B,类C通过接口interface依赖类D,如果接口interface对于类A和类B来说不是最小接口,则类B和类D必须去实现他们不需要的方法。
- 需注意:
-
- 接口尽量小,但是要有限度。对接口进行细化可以提高程序设计灵活性,但是如果过小,则会造成接口数量过多,使设计复杂化。所以一定要适度
- 提高内聚,减少对外交互。使接口用最少的方法去完成最多的事情
- 为依赖接口的类定制服务。只暴露给调用的类它需要的方法,它不需要的方法则隐藏起来。只有专注地为一个模块提供定制服务,才能建立最小的依赖关系。
5、迪米特原则
- 核心思想:类间解耦。
- 通俗来讲: 一个类对自己依赖的类知道的越少越好。自从我们接触编程开始,就知道了软件编程的总的原则:低耦合,高内聚。无论是面向过程编程还是面向对象编程,只有使各个模块之间的耦合尽量的低,才能提高代码的复用率。低耦合的优点不言而喻,但是怎么样编程才能做到低耦合呢?那正是迪米特法则要去完成的。
6、开闭原则
- 核心思想:尽量通过扩展软件实体来解决需求变化,而不是通过修改已有的代码来完成变化
- 通俗来讲: 一个软件产品在生命周期内,都会发生变化,既然变化是一个既定的事实,我们就应该在设计的时候尽量适应这些变化,以提高项目的稳定性和灵活性。
设计原则概括
单一职责原则告诉我们实现类要职责单一;里氏替换原则告诉我们不要破坏继承体系;依赖倒置原则告诉我们要面向接口编程;接口隔离原则告诉我们在设计接口的时候要精简单一;迪米特法则告诉我们要降低耦合。而开闭原则是总纲,他告诉我们要对扩展开放,对修改关闭。
二、23种设计模式
------------------------------创建型模式------------------------------
1、单例模式
单例模式确保一个类只有一个实例,自行初始化并向整个系统提供这个实例。一个类只有一个实例减少了内存开支,特别是一个对象频繁创建销毁时。单例模式可细分为2种模式,饿汉式和懒汉式,二者的区别是初始化实例的时间不同,前者是在定义时就初始化,后者是在第一次使用时初始化,这里注意多线程的并发访问。
>>单例模式示例代码<<
2、工厂方法模式
工厂方法(Factory Method)模式的意义是定义一个创建产品对象的工厂接口,将实际创建工作推迟到子类当中。核心工厂类不再负责产品的创建,这样核心类成为一个抽象工厂角色,仅负责具体工厂子类必须实现的接口,这样进一步抽象化的好处是使得工厂方法模式可以使系统在不修改具体工厂角色的情况下引进新的产品。
>>工厂方法示例代码<<
3、抽象工厂模式
为创建一组相关或相互依赖的对象提供一个接口,而且无须指定它们的具体类,可以理解为工厂方法的升级版。
>>抽象工厂示例代码<<
4、建造者模式
建造者模式也叫作生成器模式,将一个复杂对象的构建和它的表示分离,使得同样的构建过程可以创建不同的表示。比如一个User类的属性有name、age、address、email、job...等,如果想创建一个User对象,传入全部的属性有点太长了,这时可以使用建造者模式,传入一个参数就只设置对应属性的值。
>>建造者模式示例代码<<
5、原型模式
原型模式的定义为:用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。原型模式的核心是一个clone()方法,通过该方法进行对象的复制,Java提供了一个Cloneable来标示这个对象是可拷贝的,为什么说是“标示”呢?这个接口只是一个标记作用,在JVM中具有这个标记的对象才有可能被拷贝。那怎么才能从“有可能被拷贝”到“可以被拷贝”呢?方式就是覆盖clone()方法。
>>原型模式<<
------------------------------结构型模式------------------------------
6、适配器模式
适配器模式定义:将一个类的接口变换为客户端所期待的另一种接口,从而使原本因接口不匹配而无法在一起工作的两个类能够在一起工作。
适配器模式的注意事项
适配器模式最好在详细设计阶段不要考虑它, 它不是为了解决还处在开发阶段的问题,而是解决正在服役的项目问题,没有一个系统分析师会在做详细设计的时候考虑使用适配器模式,这个模式使用的主要场景是扩展应用中,就像我们上面的那个例子一样,系统扩展了,不符合原有设计的时候才考虑通过适配器模式减少代码修改带来的风险。
>>适配器模式示例代码<<
7、装饰模式
动态地给一个对象添加额外的职责,就增加功能来说,装饰模式相比于生成子类更加灵活。装饰模式相当于在一个构建类外层添加了一个包裹类。
>>装饰模式示例代码<<
8、代理模式
代理模式是一种使用率非常高的模式,为其他对象提供一种代理以控制对这个对象的访问。
在Java中有一种动态代理技术,动态代理指实现阶段不用关心代理谁,而在运行阶段才指定代理哪一个对象。
>>代理模式示例代码<<
9、组合模式
组合模式将对象组合成树形结构以表示“整体-部分”的层次结构,使得用户对单个对象和组合对象的使用具有一致性。
- Component抽象构件角色:定义参加组合对象的共有方法和属性, 可以定义一些默认的行为或属性, 比如我们例子中的getInfo就封装到了抽象类中。
- Leaf叶子构件:叶子对象, 其下再也没有其他的分支, 也就是遍历的最小单位。
- Composite树枝构件:树枝对象, 它的作用是组合树枝节点和叶子节点形成一个树形结构。
>>组合模式示例代码<<
10、门面模式
门面模式要求一个子系统的外部与其内部的通信必须通过一个统一的对象进行。门面模式提供一个高层次的接口,使得子系统更易于使用。
- Facade门面角色:客户端可以调用这个角色的方法。 此角色知晓子系统的所有功能和责任。 一般情况下,本角色会将所有从客户端发来的请求委派到相应的子系统去, 也就说该角色没有实际的业务逻辑, 只是一个委托类。
- subsystem子系统角色:可以同时有一个或者多个子系统。 每一个子系统都不是一个单独的类, 而是一个类的集合。 子系统并不知道门面的存在。 对于子系统而言, 门面仅仅是另外一个客户端而已。
>>门面模式示例代码<<
11、享元模式
享元模式是池技术的重要实现,使用共享对象可有效地支持大量的细粒度的对象。
>>享元模式示例代码<<
12、桥接模式
桥梁模式定义:将抽象和实现解耦,使得两者可以独立的变化。
- Abstraction——抽象化角色:它的主要职责是定义出该角色的行为, 同时保存一个对实现化角色的引用, 该角色一般是抽象类。
- Implementor——实现化角色:它是接口或者抽象类, 定义角色必需的行为和属性。
- RefinedAbstraction——修正抽象化角色:它引用实现化角色对抽象化角色进行修正。
- ConcreteImplementor——具体实现化角色:它实现接口或抽象类定义的方法和属性。
>>桥接模式示例代码<<
------------------------------行为模式------------------------------
13、责任链模式
责任链模式定义:使多个对象都有机会处理请求,从而避免了请求的发送者和接收者之间的耦合关系。将这些对象组成一条链,并沿着这条链来传递请求,直到有对象处理它为止。
>>责任链模式示例代码<<
14、迭代器模式
迭代器模式提供了一种方法访问一个容器对象中各个元素,而又不暴露该对象的内部细节(目前已很少用到了,容器一般都提供了迭代器)。
>>迭代器模式示例代码<<
15、中介者模式
用一个中介对象封装一系列的对象交互,中介者使各对象不需要显示地互相作用,从而使其耦合松散,而且可以独立地改变它们之间的交互。
终结者模式的优点:减少类间的依赖,把原来的一对多的依赖变成了一对一的依赖,同事类只依赖中介者,较少了依赖,降低了类间的耦合。
终结者模式的缺点:终结者会膨胀的很大,而且逻辑很复杂,原来N个对象直接的相互依赖关系转换为中介者和同事类的依赖关系,同事类越多,中介者的逻辑越复杂。
>>中介者模式设计模式<<
16、模板方法
定义一个操作中算法的框架,而将一些步骤延迟到子类中。使得子类可以不改变一个算法即可重定义该算法的某些特定步骤。
基本方法:也叫作基本操作,是由子类实现的方法,并且在模板方法被调用
模板方法:可以有一个或几个, 一般是一个具体方法, 也就是一个框架, 实现对基本方法的调度,完成固定的逻辑。一般将模板方法加上final关键字,不允许被覆写
>>模板方法示例代码<<
17、状态模式
状态模式定义:当一个对象内在状态改变时允许其改变行为,这个对象看起来像改变了其类。状态模式的核心是封装,状态的变更引起了行为的变更,从外部看起来就好像这个对象对应的类发生了改变一样。
● State——抽象状态角色
接口或抽象类, 负责对象状态定义, 并且封装环境角色以实现状态切换。
● ConcreteState——具体状态角色
每一个具体状态必须完成两个职责:本状态的行为管理以及趋向状态处理,通俗地说,就是本状态下要做的事情, 以及本状态如何过渡到其他状态。
● Context——环境角色
定义客户端需要的接口, 并且负责具体状态的切换。
>>状态模式示例代码<<
18、策略模式
策略模式是比较简单的模式,定义一组算法,将每个算法封装起来,并且使它们之间可以互换。
>>策略模式示例代码<<
19、备忘录模式
备忘录模式定义:在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。这样以后就可将该对象恢复到原先保存的状态。
>>备忘录模式示例代码<<
20、观察者模式
观察者模式(Observer Pattern)也叫作发布订阅模式,其定义如下:定义对象间一种一对多的依赖关系,使得每当一个对象改变状态,则所有依赖于它的对象都会得到通知并自动更新。
>>观察者模式示例代码<<
21、访问者模式
封装一些作用于某种数据结构中的个元素的操作,它可以在不改变数据结构的前提下定义作用于这些元素的新的操作。
>>访问者模式示例代码<<
22、命令模式
命令模式是一个高内聚的模式,其定义为:将一个请求封装成一个对象,从而让你使用功能不同的请求把客户端参数化,对请求排列或记录请求日志,可以提供命令的撤销和恢复功能。
>>命令模式示例代码<<
23、解释器模式
解释器模式定义:给定一门语言, 定义它的文法的一种表示, 并定义一个解释器, 该解释器使用该表示来解释语言中的句子。
参考:
1、《设计模式之禅》
2、《设计模式-可复用面对对象软件的基础》
3、https://github.com/luoxn28/ThinkInTechnology/tree/master/DesignPattern