本文是对设计模式的经验与总结
之前在网上看到对于设计模式写的很好的文章,特意贴过来与大家分享,点击此处可查看原文。
一、概述
面向对象编程有七大原则,即经常提到的 Design Pattern,提倡它的根本原因是为了代码复用,增加可维护性。设计模式就是实现了这些原则,从而达到了代码复用、增加可维护性的目的。
设计模式是一套被反复使用的、多数人知晓的、经过分类编目的、代码设计经验的总结。使用设计模式是为了重用代码、让代码更容易被他人理解、保证代码可靠性。 毫无疑问,设计模式于己于他人于系统都是多赢的,设计模式使代码编制真正工程化,设计模式是软件工程的基石,如同大厦的一块块砖石一样。
二、7 个设计原则
因为设计模式就是基于这些原则的实现,所以很有必要了解这些原则,下面主要对面向对象编程的几个原则进行简单介绍。
单一职责原则 ( SRP )
英文全称是 Single Responsibility Principle,定义是一个类,应该只有一个引起它变化的原因。类变化的原因就是职责,如果一个类承担的职责过多,就等于把这些职责耦合在一起了。一个职责的变化可能会削弱或者抑制这个类完成其他职责的能力。这种耦合会导致脆弱的设计,当发生变化时,设计会遭受到意想不到的破坏。而如果想要避免这种现象的发生,就要尽可能的遵守单一职责原则。此原则的核心就是解耦和增强内聚性。
开闭原则 ( OCP )
英文全称是 Open Close Principle,定义是软件实体(包括类、模块
、函数等)应该对于扩展时开放的,对于修改是封闭的。开闭原则是是面向对象设计中最重要的原则之一,其它很多的设计原则都是实现开闭原则的一种手段。
里氏替换原则 ( LSP )
英文全称是 Liskov Substitution Principle,是面向对象设计的基本原则之一。 定义是任何基类可以出现的地方,子类一定可以出现。LSP 是继承复用的基石,只有当派生类可以替换掉基类,且软件单位的功能不受到影响时,基类才能真正被复用,而派生类也能够在基类的基础上增加新的行为。里氏替换原则是对开闭原则的补充。实现开闭原则的关键步骤就是抽象化,而基类与子类的继承关系就是抽象化的具体实现,所以里氏替换原则是对实现抽象化的具体步骤的规范。
依赖倒置原则 ( DIP )
英文全称是 Dependence Inversion Principle,这个原则是开闭原则的基础,依赖倒置原则就是要求调用者和被调用者都依赖抽象,这样两者没有直接的关联和接触,在变动的时候,一方的变动不会影响另一方的变动。依赖倒置强调了抽象的重要性,针对接口编程,依赖于抽象而不依赖于具体。
接口隔离原则 ( ISP )
英文全称是 Interface Segregation Principle,这个原则的意思是使用多个隔离的接口,比使用单个接口要好。目的就是降低类之间的耦合度,便于软件升级和维护。
最少知道原则(迪米特原则)
一个实体应当尽量少地与其他实体之间发生相互作用,使得系统功能模块相对独立。通俗地说就是不要和陌生人说话,即一个对象应对其他对象有尽可能少的了解。迪米特法则的初衷在于降低类之间的耦合。由于每个类尽量减少对其他类的依赖,因此,很容易使得系统的功能模块功能独立,相互之间不存在(或很少有)依赖关系。
合成/聚合复用(CARP)
英文全称是 Composite Reuse Principle,合成/聚合复用原则经常又叫做合成复用原则。合成/聚合复用原则的潜台词是:我只是用你的方法,我们不一定是同类。继承的耦合性更大,比如一个父类后来添加实现一个接口或者去掉一个接口,那子类可能会遭到毁灭性的编译错误,但如果只是组合聚合,只是引用类的方法,就不会有这种巨大的风险,同时也实现了复用。
三、创建型模式 ( 5种 )
创建型模式是指这些设计模式提供了一种在创建对象的同时隐藏创建逻辑的方式,而不是使用新的运算符直接实例化对象。这使得程序在判断针对某个给定实例需要创建哪些对象时更加灵活。
1.单例模式
- 定义
确保某一个类只有一个实例,并自行实例化向整个系统提供这个实例。- 简介
单例模式理解起来不难,典型例子有一个公司只能有一个 CEO。它主要是为了保证一个类仅有一个实例,这个类中自己提供一个返回实例的方法,方法中先判断系统是否已经有这个单例,如果有则返回,如果没有则创建。如果创建多个实例会消耗过多的资源或者某种类型的对象只应该有且只有一个时,应该考虑使用单例模式。- 实现
单例模式理解起来不难,重要的是需要掌握它的几种常见写法。
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455 写法一、懒汉式写法public class Singleton {private static Singleton instance;//构造函数私有private Singleton (){}public static synchronized Singleton getInstance() {if (instance == null) {instance = new Singleton();}return instance;}}写法二、DCL(Double Check Lock) 双重校验锁public class Singleton {private volatile static Singleton singleton;private Singleton (){}public static Singleton getSingleton() {if (singleton == null) {synchronized (Singleton.class) {if (singleton == null) {singleton = new Singleton();}}}return singleton;}}写法三、静态内部类单例模式public class Singleton {private Singleton (){}public static final Singleton getInstance() {return SingletonHolder.INSTANCE;}private static class SingletonHolder {private static final Singleton INSTANCE = new Singleton();}}
上面只列出了单例模式常见的三种写法,单例模式还有好几种别的写法,这里就不全部列出了。上面的第一种懒汉式写法做到了延迟创建和线程安全,缺点是每次调用 getInstance() 时都必须进行同步,效率不佳。第二种 DCL 方式比较常见,两次判空,第一次判空避免了不必要的同步,第二次保证了单例创建,这种方式比较不错,但是在高并发环境下有时会出现问题。第三种方法最被推荐,线程安全也保证了实例唯一。
2.工厂方法模式
- 定义
定义一个用于创建对象的接口,让子类决定实例化哪一个类。 - 举例
工厂方法模式的典型例子,自行车分为山地自行车和公路自行车等,当需要买自行车时,我们直接去自行车厂里告诉厂长我们需要的自行车即可。 - 例子1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556571、定义一个接口自行车,Bike。public interface Bike{void ride();}2、定义实现类山地自行车,MBikepublic class MBike implements Bike{public void draw() {System.out.println("MBike Rides... ");}}3、定义实现类公路自行车,RBikepublic class RBike implements Bike{public void draw() {System.out.println("RBike Rides... ");}}4、工厂类,负责创建对象public class BikeFactory {//使用 getShape 方法获取形状类型的对象public Bike getBike(String bikeType){if(bikeType == null){return null;}if(bikeType.equalsIgnoreCase("MBike")){return new MBike();}else if(shapeType.equalsIgnoreCase("RBike")){return new RBike();}return null;}}5、子类决定实例化哪一个类public class FactoryPatternDemo {public static void main(String[] args) {BikeFactory bikeFactory = new BikeFactory();Bike bike1 = bikeFactory.getBike("MBike");bike1.ride();Bike bike2= bikeFactory.getBike("RBike");bike2.ride();}}
3.抽象工厂模式
- 定义
提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类。
抽象工厂模式主要解决接口选择的问题,典型例子是手机和电脑问题,假设抽象产品1是手机,具体产品是 Android 手机和 iOS 手机,抽象产品2是电脑,具体产品是 Windows 电脑和 Mac 电脑,抽象工厂是同时生产手机和电脑的公司,具体工厂是联想公司和苹果公司。
抽象工厂的优点是当一个产品族中的多个对象被设计成一起工作时,它能保证客户端始终只使用同一个产品族中的对象。缺点是产品族扩展非常困难,要增加一个系列的某一产品,既要在抽象工厂里加代码,又要在具体工厂里加代码。
4.建造者模式
- 定义
将一个复杂的构建与其表示相分离,使得同样的构建过程可以创建不同的表示。- 简介
建造者模式主要解决在软件系统中一个复杂对象的创建工作。通常一个复杂对象是由各个部分的子对象用一定的算法构成,由于需求的变化,这个复杂对象的各个部分经常面临着剧烈的变化,但是将它们组合在一起的算法却相对稳定。
建造者模式的一个典型例子是Android中的AlertDialog的构建过程。还有个例子是计算机的组装,计算机是个复杂的对象,它是有很多零件组装而成,显示器、操作系统,鼠标等,通过创建Builder接口来控制零件的组装过程,这样当组件发生变化时,虽然经过同样的构建过程,但是最后得到的结果不同。
5.原型模式
结构型模式关注类和对象的组合。继承的概念被用来组合接口和定义组合对象获得新功能的方式。
1.适配器模式
- 定义
将一个类的接口转换成另外一个客户希望的接口。适配器模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。- 简介
适配器模式包括两种类型,类适配器模式和对象适配器模式。这里主要介绍对象适配器模式,因为它更灵活。适配器模式中主要有三个部分,Target、Adapter 和 Adaptee,其中 Target 是目标角色,Adaptee 是需要被转换的对象,Adapter 就是适配器。例如,现在有个手机充电电压是
5v,而插座电压是 220v,这时我们可以把充电器看成是 Adapter,它将电压进行转化最后得到结果。- 例子
123456789101112131415161718192021222324252627282930 // Target 目标接口public interface Volt5 {public int getVolt5();}// Adaptee 需要被转换的对象public class Volt220 {public int getVolt220() {return 220;}}//Adapter 适配器public class VoltAdapter implements Volt5 {Volt220 mVolt220;public VoltAdapter(Volt220 adaptee) {mVolt220 = adaptee;}public int getVolt220() {return 220;}public int getVolt5() {return 5;}}2.桥接模式
- 桥接模式
将抽象部分与实现部分分离,使它们都可以独立的变化。- 简介
在软件系统中,某些类型由于自身的逻辑,它具有两个或多个维度的变化,那么如何应对这种“多维度的变化”,这就要使用桥接模式。桥接模式需要重点理解的抽象部分,实现部分,脱耦。一个典型的例子是咖啡加糖问题,抽象部分有 Coffee,其下有 LargeCoffee,SmallCoffee,实现部分是 CoffeeAdd,其下有 Sugar,Normal,抽象类 Coffee 中引用
CoffeeAdd,这样 CoffeeAdd 其实就是一个桥接。
3.装饰模式- 定义
动态地给一个对象添加一些额外的职责。就增加功能来说,Decorator模式相比生成子类更为灵活。- 简介
装饰模式是作为现有的类的一个包装,它允许向一个现有的对象添加新的功能,同时又不改变其结构。这种模式创建了一个装饰类,用来包装原有的类,并在保持类方法签名完整性的前提下,提供了额外的功能。装饰模式主要是装饰器类 Decorator 的设计,它对指定对象进行装饰。
4.组合模式- 定义
将对象组合成树形结构以表示“部分-整体”的层次结构,使得用户对单个对象和组合对象的使用具有一致性。- 简介
组合模式理解起来相对简单,典型的例子就是假设公司 A,里面有不同的部门,不同的部分下有不同的员工,这样一个部门下的所有员工组合成了一个部门,所有部门组合成了整个公司。
5.外观模式- 定义
为子系统中的一组接口提供一个一致的界面,外观模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。- 简介
外观模式的一个典型例子是去医院看病,挂号、门诊、划价、取药,让患者或患者家属觉得很复杂,如果有提供接待人员,只让接待人员来处理,就很方便。
6.享元模式- 定义
运用共享技术有效地支持大量细粒度的对象。- 简介
在有大量对象时,有可能会造成内存溢出,我们把其中共同的部分抽象出来,如果有相同的业务请求,直接返回在内存中已有的对象,避免重新创建。
7.代理模式- 定义
为其他对象提供一种代理以控制对这个对象的访问。- 简介
代理模式主要解决在直接访问对象时带来的问题。举个例子,猪八戒去找高翠兰结果是孙悟空变的,可以这样理解:把高翠兰的外貌抽象出来,高翠兰本人和孙悟空都实现了这个接口,猪八戒访问高翠兰的时候看不出来这个是孙悟空,所以说孙悟空是高翠兰代理类。五、行为型模式 ( 11种 )
这些设计模式特别关注对象之间的通信。
1.模板方法模式
- 定义
一个操作中的算法的框架,而将一些步骤延迟到子类中,使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。- 例子
模板方法模式一个典型例子就是 Android 中的异步任务类 AsyncTask,它对异步任务的执行进行了流程封装,子类继承它时,只需在指定的流程中实现具体的操作即可。
2.命令模式- 定义
将一个请求封装为一个对象,从而可用不同的请求对客户进行参数化;对请求排队或记录请求日志,以及支持可取消的操作。- 简介
命令模式主要是通过调用者调用接受者执行命令,这个模式中需要理解的是三个角色:(1) Receiver 真正的命令执行对象 (2) Command 持有一个对 Receiver 的引用,调用 Receiver 的相关方法。(3) Invoker 请求者,持有一个对 Command 的引用,调用 Command 的方法执行具体命令。
3.迭代器模式- 定义
提供一种方法顺序访问一个聚合对象中各个元素, 而又不需暴露该对象的内部表示。- 简介
在 Java 集合框架中我们知道对于一个指定的集合类,我们可以使用一个特定的 Iterator 迭代器来对集合中的所有元素进行遍历。这样结合来看,迭代器模式很好理解了。
4.观察者模式- 定义
定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。- 简介
观察者模式可以结合 Android 中的 ListView 来理解,ListView 关联的适配器 Adapter 在数据发生变化时会通过 notifyDataSetChanged() 方法来通知界面刷新。
5.中介者模式- 定义
用一个中介对象来封装一系列的对象交互。中介者使各对象不需要显式地相互引用,从而使其耦合松散,而且可以独立地改变它们之间的交互。- 简介
中介者模式的典型例子就是未加入 WTO 之前各个国家相互贸易,结构复杂,大家都加入WTO后是各个国家通过 WTO 来互相贸易,变得规范。
6.备忘录模式- 定义
在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。这样以后就可将该对象恢复到保存的状态。- 简介
备忘录模式的典型例子就是git版本管理工具,它帮我们保存了每次提交后的项目状态,在必要的时候我们可以回退到指定的版本中。
7.解释器模式- 定义
给定一个语言,定义它的文法的一种表示,并定义一个解释器,这个解释器使用该表示来解释语言中的句子。- 简介
解释器的典型例子是在编译原理中的应用,如果一种特定类型的问题发生的频率足够高,那么可能就值得将该问题的各个实例表述为一个简单语言中的句子。这样就可以构建一个解释器,该解释器通过解释这些句子来解决该问题。
8.状态模式- 定义
允许一个对象在其内部状态改变时改变它的行为。对象看起来似乎修改了它的类。- 简介
状态模式主要解决对象的行为依赖于它的状态(属性),并且可以根据它的状态改变而改变它的相关行为。典型的例子是一个人在不同的状态下完成一件事的结果可能是不同的。
9.策略模式- 定义
定义一系列的算法,把它们一个个封装起来, 并且使它们可相互替换。本模式使得算法可独立于使用它的客户而变化。- 简介
定义一系列的算法,把它们一个个封装起来, 并且使它们可相互替换。本模式使得算法可独立于使用它的客户而变化。
10.责任链模式- 定义
使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系。将这些对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理它为止。- 简介
责任链模式,避免请求发送者与接收者耦合在一起,让多个对象都有可能接收请求,将这些对象连接成一条链,并且沿着这条链传递请求,直到有对象处理它为止。
11.访问者模式- 定义
封装一些作用于某种数据结构中的各元素的操作。它使你可以在不改变各元素的类的前提下定义作用于这些元素的新操作。- 简介
访问者模式是一种将数据操作和数据结构分离的设计模式,它通常使用在对象结构比较稳定,但是经常需要在此对象结构上定义新的操作,或者需要对一个对象结构中的对象进行很多不同的并且不相关的操作,而需要避免让这些操作”污染”这些对象的类,使用访问者模式将这些封装到类中。