历史起源
与很多软件工程技术一样,模式起源于建筑领域,软件工程只有短短的几十年,与已经拥有几千年底蕴的建筑工程相比,后者有太多值得学习和借鉴的地方。
哈佛大学的建筑学博士克里斯托弗.亚历山大,是建筑学领域的模式之父。他与其研究团队用了约20年的时间,对住宅和周边环境进行了大量的调查研究,发现人们对舒适住宅和城市环境存在一些共同的认同规律,将它们归纳成253个模式。对每一个模式都从前提条件、目标问题、 解决方案三个方面进行了描述,并给出了从需求分析到结构设计再到经典实例的过程模型。
所以,对模式的定义可以抽象为在特定环境下,人们为了解决某类重复出现问题,而总结归纳出来的有效解决方案。
GoF将模式的概念引入软件工程领域,这标志着软件模式的诞生。软件模式并非仅限于设计模式,还包括架构模式、分析模式和过程模式等,实际上,在软件开发生命周期的每一个阶段都存在着一些被认同的模式。
软件模式主要由四部分构成,包括待解决问题、约束条件、解决方案、优点。
软件模式与具体的应用领域无关,也就是说无论从事的是移动开发、桌面开发、Web开发还是嵌入式软件的开发,都可以使用软件模式。
在软件模式中,设计模式是研究最为深入的分支,它融合了众多专家的设计经验,已经在成千上万的软件中得以应用。 1995年, GoF将收集和整理好的23种设计模式汇编成了一本名叫《设计模式》的书,该书的出版也标志着设计模式正式成为面向对象版软件工程的一个重要研究分支。
设计原则
设计原则是设计模式背后所遵循的抽象思想,所以在说设计模式之前,需要讲讲设计原则。
总原则:开闭原则(Open Close Principle)
开闭原则就是说对扩展开放,对修改关闭。在程序需要进行拓展的时候,不能去修改原有的代码,而是要扩展原有代码,实现一个热插拔的效果。所以一句话概括就是:为了使程序的扩展性好,易于维护和升级。想要达到这样的效果,我们需要使用接口和抽象类等,后面的具体设计中我们会提到这点。
单一职责原则(Single Responsibility Principle)
不要存在多于一个导致类变更的原因,也就是说每个类应该实现单一的职责,如若不然,就应该把类拆分。
里氏替换原则(Liskov Substitution Principle)
里氏代换原则(Liskov Substitution Principle LSP)面向对象设计的基本原则之一。 里氏代换原则中说,任何基类可以出现的地方,子类一定可以出现。 LSP是继承复用的基石,只有当衍生类可以替换掉基类,软件单位的功能不受到影响时,基类才能真正被复用,而衍生类也能够在基类的基础上增加新的行为。里氏代换原则是对“开-闭”原则的补充。实现“开-闭”原则的关键步骤就是抽象化。而基类与子类的继承关系就是抽象化的具体实现,所以里氏代换原则是对实现抽象化的具体步骤的规范。—— From Baidu 百科
历史替换原则中,子类对父类的方法尽量不要重写和重载。因为父类代表了定义好的结构,通过这个规范的接口与外界交互,子类不应该随便破坏它。
依赖倒转原则(Dependence Inversion Principle)
这个是开闭原则的基础,具体内容:面向接口编程,依赖于抽象而不依赖于具体。写代码时用到具体类时,不与具体类交互,而与具体类的上层接口交互。
接口隔离原则(Interface Segregation Principle)
这个原则的意思是:每个接口中不存在子类用不到却必须实现的方法,如果不然,就要将接口拆分。使用多个隔离的接口,比使用单个接口(多个接口方法集合到一个的接口)要好。
迪米特法则(最少知道原则)(Demeter Principle)
就是说:一个类对自己依赖的类知道的越少越好。也就是说无论被依赖的类多么复杂,都应该将逻辑封装在方法的内部,通过public方法提供给外部。这样当被依赖的类变化时,才能最小的影响该类。
最少知道原则的另一个表达方式是:只与直接的朋友通信。类之间只要有耦合关系,就叫朋友关系。耦合分为依赖、关联、聚合、组合等。我们称出现为成员变量、方法参数、方法返回值中的类为直接朋友。局部变量、临时变量则不是直接的朋友。我们要求陌生的类不要作为局部变量出现在类中。
合成复用原则(Composite Reuse Principle)
原则是尽量首先使用合成/聚合的方式,而不是使用继承。
设计模式
设计模式可以分成以下几类
模式类型 | 模式名称 |
---|---|
创建型模式 | 抽象工厂、建造者模式、工厂方法、原型模式、单例模式 |
结构型模式 | 适配器模式、桥接模式、组合模式、装饰者模式、外观模式、享元模式、代理模式 |
行为型模式 | 观察者模式、模板方法、命令模式、状态模式、职责链模式、解释器模式、中介者模式、访问者模式、策略模式、备忘录模式、迭代器模式 |
抽象工厂模式(Abstract Factory)
提供一个创建一系列相关或互相依赖对象的接口,而无需指定它们具体的类。
设计原则
里氏替换原则
使用场景
系统的产品有多于一个的产品族,而系统只消费其中某一族的产品。
优点
当一个产品族中的多个对象被设计成一起工作时,它能保证客户端始终只使用同一个产品族中的对象。
缺点
产品族扩展非常困难,要增加一个系列的某一产品,既要在抽象的 Creator 里加代码,又要在具体的里面加代码。
应用实例
- jdk中连接数据库的代码是典型的抽象工厂模式,每一种数据库只需提供一个统一的接口:Driver(工厂类),并实现其中的方法即可。不管是jdbc还是odbc都能够通过扩展产品线来达到连接自身数据库的方法。
- java.util.Collection 接口中定义了一个抽象的 iterator() 方法,该方法就是一个工厂方法。对于 iterator() 方法来说 Collection 就是一个抽象工厂。
建造者模式(Builder)
将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。
设计原则
依赖倒转原则
应用场景
一些基本部件不会变,而其组合经常变化的时候。
优点
- 建造者独立,易扩展。
- 便于控制细节风险。
缺点
- 产品必须有共同点,范围有限制。
- 如内部变化复杂,会有很多的建造类。
应用实例
- 去肯德基,汉堡、可乐、薯条、炸鸡翅等是不变的,而其组合是经常变化的,生成出所谓的"套餐"。
- JAVA 中的 StringBuilder。
工厂方法模式(Factory Method)
定义一个用于创建对象的接口,让子类决定实例化哪一个类,工厂方法使一个类的实例化延迟到其子类。
设计原则
开放封闭原则
应用场景
不改变工厂和产品体系,只是要扩展产品(变化)。
优点
- 一个调用者想创建一个对象,只要知道其名称就可以了。
- 扩展性高,如果想增加一个产品,只要扩展一个工厂类就可以。
- 屏蔽产品的具体实现,调用者只关心产品的接口。
缺点
每次增加一个产品时,都需要增加一个具体类和对象实现工厂,使得系统中类的个数成倍增加,在一定程度上增加了系统的复杂度,同时也增加了系统具体类的依赖。
应用实例
- java.lang.Class中的newInstance方法
- java.lang.Boolean中的valueOf方法
原型模式(prototype)
用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。
设计原则
应用场景
在初始化信息不发生变化的情况,用克隆进行拷贝。
优点
- 性能提高。
- 逃避构造函数的约束。
缺点
- 配备克隆方法需要对类的功能进行通盘考虑,这对于全新的类不是很难,但对于已有的类不一定很容易,特别当一个类引用不支持串行化的间接对象,或者引用含有循环结构的时候。
- 必须实现 Cloneable 接口。
应用实例
java.lang.Object中的clone() 方法。
单例模式(Singleton)
保证一个类仅有一个实例,并提供一个访问它的全局访问点。
设计原则
应用场景
一些设备管理器常常设计为单例模式,比如一个电脑有两台打印机,在输出的时候就要处理不能两台打印机打印同一个文件
优点
- 在内存里只有一个实例,减少了内存的开销,尤其是频繁的创建和销毁实例(比如管理学院首页页面缓存)。
2.避免对资源的多重占用(比如写文件操作)。
缺点
没有接口,不能继承,与单一职责原则冲突,一个类应该只关心内部逻辑,而不关心外面怎么样来实例化。
应用实例
java.lang.Runtime就是典型单例的运用