设计模式之创建型模式
一、前言
设计模式应该是在软件工程的背景下进行讨论的,而不是为了设计模式而论设计模式。设计模式是软件工程面向对象设计工作中一个抽象、归纳、总结的过程。软件设计的最高目标和最高准则就是易扩展,易复用,易维护,
灵活性高,高可用,稳定性高一切的设计原则和设计模式最终的目标都是向这个目标靠拢的。
二、面向对象设计原则
任何的设计模式都是基于面向对象的特性(抽象、封装、继承、多态)和设计原则进行设计和实现的,是面向对象设计原则的在不同场景下的实现方案。
抽象:抽离变化的部分,引入抽象层,实现变化部分的抽象。在实际的应用场景中使用抽象层而不是具体的某一实现类(具体的变化),实现具体实现类(某一变化)与使用场景分离,降低耦合度,易扩展
封装:封装变化的部分与不变的部分,明确职责与组成结构
继承:实现可扩展的目标
多态:抽象层与具体实现类(具体变化)之间的类型转换
设计原则 |
内容 |
备注 |
目标 |
单一职责原则 (SRP) |
就一个类而言,应该仅有一个引起它变化的原因。一个类只担负一种职责,以降低职责之间的耦合度,提供当前代码的复用性 | 严格遵循了高内聚低耦合的指导方针 用于实现对类的粒度大小进行控制 |
可复用、易维护 |
开放封闭原则 (OCP) |
软件实体(类、模块、方法等)应该可以扩展,但是不可以修改。一个软件实体应该对扩展开放,对修改关闭,即软件实体应该尽量在不修改原有的代码下进行扩展。 目标是是即能在严格保证当前软件系统稳定性的前提下,又能够实现对软件系统根据业务需求不断进行扩展。 |
将软件中变化的部分抽离出来,在使用场景中使用抽象化代替(降低具体实现类与使用场景的耦合度),而将具体的变化的细节封装在不同的实现类中,实现在不修改原有代码只需要将新增变化的部分扩充至抽象化内。 | 稳定,易扩展 |
依赖倒转原则 ( DIP) |
抽象不应该依赖于细节,细节应该依赖于抽象。即要针对接口编程,而不是针对实现编程。 | 依赖倒转原则要求我们在程序代码中传递参数时或在关联关系中,尽量引用层次高的抽象层类,即使用接口和抽象类进行变量类型声明、参数类型声明、方法返回类型声明,以及数据类型的转换等,而不要用具体类来做这些事情。 将变化的部分抽离出来用抽象类代替,降低变化的部分与其他不变化部分的耦合度。 |
可扩展 |
里氏替换原则 (LSP) |
所有引用基类(父类)的地方必须能透明地使用其子类的对象。 | 里氏代换原则是实现开闭原则的重要方式之一,由于使用基类对象的地方都可以使用子类对象,因此在程序中尽量使用基类类型来对对象进行定义,而在运行时再确定其子类类型,用子类对象来替换父类对象。 | |
迪米特原则 (LoD) 最少知识原则(LKP) |
一个软件实体应当尽可能少地与其他实体发生相互作用 如果两个类不必彼此直接通信,那么这两个类就不应当直接的相互引用。如果其中的一个类需要调用另外一个类的某一个方法的话,可以通过第三者转发这个调用。(通过第三方来降低两个类之间的耦合度) |
在类的划分上,应当尽量创建松耦合的类,类之间的耦合度越低,就越有利于复用,一个处在松耦合中的类一旦被修改,不会对关联的类造成太大波及;在类的结构设计上,每一个类都应当尽量降低其成员变量和成员函数的访问权限;在类的设计上,只要有可能,一个类型应当设计成不变类;在对其他类的引用上,一个对象对其他对象的引用应当降到最低。 | 可复用 |
接口隔离原则 (ISP) |
使用多个专门的接口,而不使用单一的总接口,即客户端不应该依赖那些它不需要的接口。 |
在使用接口隔离原则时,我们需要注意控制接口的粒度,接口不能太小,如果太小会导致系统中接口泛滥,不利于维护;接口也不能太大,太大的接口将违背接口隔离原则,灵活性较差,使用起来很不方便。一般而言,接口中仅包含为某一类用户定制的方法即可,不应该强迫客户依赖于那些它们不用的方法。 | |
合成/聚合复用原则 (CARP) |
尽量使用合成/聚合,尽量不要使用类继承。聚合表示一种弱的“拥有”关系,合成表示一种强的关系,体现了严格的局部与整体的关系。 | 使用合成/聚合复用原则的好处是,优先使用对象的合成/聚合将有利于保持每个类被封装,并被集中在单个任务上。这样类和类继承层次都会保持比较小的规模,不会造成类膨胀,不太可能造成类增长为不可控制的庞然大物。 |
三、创建型模式概述
创建型模式抽象了实例化过程,实现一个系统独立于如何创建、组合和表示它的那些对象。一个类创建型模式使用继承改变被实例化的对象,而一个对象创建型模式将实例化委托给另外一个对象。
创建型模式都是关于该系统使用哪些具体的类的信息封装起来,都隐藏了这些类实例是如何被创建的和放在一起的。整个系统只知道抽象类所定义的接口并不清除具体的实现类。
创建型模式在什么被创建,谁创建它,它是怎样被创建的,以及何时创建这些方面给予了很多的灵活性。
四、创建型模式内容
1、简单工厂模式
(1)功能
定义一个创建对象的接口,封装了根据客户端选择动态的实例化相关类。简单工厂模式使创建对象的逻辑都封装在一个方法里面。更多是一种编程习惯模式,而不是设计模式。
(2)适用性
(3)结构
(4)参与者
(5)优缺点
优点:简单工厂类的方法里面包含了具体的逻辑判断,根据客户端的条件选择动态实例化相关的类,对客户端来说解除了与具体实现了的耦合。
缺点:没有遵循开放-封闭原则。新增新的实现类的是需要修改简单工厂类里面的逻辑判断。
2、工厂方法模式
(1)功能
定义一个用于创建对象的接口,让子类决定实例化哪一个类。工厂方法使一个类的实例化延迟到其子类。
(2)适用性
1)当一个类希望由他的子类来指定它所创建的对象的时候(即希望实例化的操作推迟到子类中)
2)当一个类不知道它所必选创建的对象的类的时候
(3)结构
(4)参与者
Product:所有具体实现类的接口,也是工厂方法所创建的对象的接口。
ConcreteProduct:继承于Product的具体对象类
Creator:声明工厂方法,返回一个Product类型的对象.可以是一个抽象类且不提供所声明工厂方法的实现,或者也可以是一个具体的类,工厂方法提供一个缺省的实现
ConcreteCreator:继承于Creator,用于创建返回ConcreteProduct实例的具体工厂方法类
(5)优缺点
1)优点:向客户端隐藏了对象实例化的细节。添加新的类不需要修改原来抽象对象和抽象工厂提供的接口,也无需修改客户端代码新增具体的实现类和具体的工厂类即可,严格遵循了开放封闭原则。
2)缺点:需要依赖客户端决定实例化哪一个工厂类。工厂方法把简单工厂内部的逻辑判断移动到了客户端来进行。每新增一个具体的实现了,就需要新增一个该类的具体工厂方法类。
(6)相关模式
1)简单工厂模式:
2)抽象工厂模式
3)模板方法模式
4)原型模式
3、原型模式
(1)功能与背景
系统中经常存在某些结构比较复杂比较庞大的又频繁使用的类对象。如果频繁的创建和销毁这些对象,势必过多占用系统空间和影响系统性能。所以通过原型模式只对现有的对象进行克隆产生新的对象,从而降低因频繁创建和销毁结构复杂的对象而带来的性能损耗。
用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。
原型模式就是从一个对象在创建另外一个可定制的对象,而且不需要指定任何创建的细节。
(2)适用性
1)当一个系统应该独立于它的产品创建,构成、表示时,使用原型模式
2)当要实例化的类时运行时刻指定时
3)为了避免创建一个和产品类层次平行的工厂类层次的时候
4)当一个类的实例只能有几个不同的状态组合中的一种时。
(3)结构
(4)参与者
Prototype:所有具体原型类的抽象接口,声明了一个克隆自己的接口
ConcretePrototype:实现Prototype接口的具体原型类,并实现了克隆自身的操作
Client:客户端 ,使用原型的地方,
(5)优缺点
1)优点:
a.当创建新的对象实例较为复杂时,使用原型模式可以简化对象的创建过程,通过复制一个已有实例可以提高新实例的创建效率
b.不用构造一个与产品类层次平行的工厂类
c.可以运行时增加和删除产品
2)缺点:
a.需要每个类实现Clone操作,可能存在困难
(6)相关模式
1)抽象工厂模式
抽象工厂可以存储一个被克隆的原型的集合并返回产品对象。
2)组合模式
3)装饰模式
4、生成器模式(建造者模式)
(1)功能与背景
生成器模式(建造者模式):将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示
对于某些复杂的对象,由多个子部件按照一定的步骤组合而成。多个子部件灵活组合按照相同的创建步骤组合成不同的对象。
它是将一个复杂的对象分解为多个简单的对象,然后一步一步构建而成。它将变与不变相分离,即产品的组成部分是不变的,但每一部分是可以灵活选择的。
(2)适用性
建造者模式使当创建复杂对象的算法应该独立于对象的组成部分以及它们的装配方式时适用的模式
1)对象复杂的构建过程应该独立于对象的表示时候
2)构造过程允许构造对象有不同的表示时
(3)结构
(4)参与者
Product(产品):表示被构造的复杂的对象类,该类包含多个组件。
Builder(抽象建造者):为创建一个Product对象的各个部件指定抽象接口,实现Product对象表示抽象接口。通常还包含一个返回复杂产品的方法 getResult()。
ConcreteBuilder(具体建造者):实现Builder接口,负责实现复杂Product对象各个组件的初始化表示的具体类。表示了一个具体表示的Product 或者说是Product对象的一个表示实例
Director(指挥者):指挥者用来控制复杂对象Proudct的构造过程,也用它来隔离用户(客户端)与建造过程的关联。封装了复杂Product对象的构造过程。在指挥者中不涉及具体产品的信息。
(5)优缺点
1)优点:
a.将对象的构造过程与对象的表示分离开来,建造者隐藏了对象是如何构造的,如果需要修改对象内部表示只需要在定义一个具体的建造者就可以了
b.统一对象的构造过程
c.生成器模式将对象的构造过程从创建该对象的类中分离出来,是用户无须了解该对象的具体组件
d.可以更加精细有效地控制对象的构造过程。生成器将对象的构造过程分解成若干个步骤,这就使程序可以更加精细,有效地控制整个对象的构造
e.生成器模式将对象的构造过程与创建该对象类解耦,使对象的创建更加灵活有弹性。
f.当增加新的具体生成器时,不必修改指挥者的代码,即该模式满足开放封闭原则。
2)缺点:
a.当Product新增新的组件的时候,需要修改指挥者类Direct、新增一个具体表示类ConcreteBuilder、以及修改旧有的Builder 和ConcreteBuilder类
(6)相关模式
1)与抽象工厂的区别:
生成器模式:侧重于复杂对象的构造过程。最后一步返回对象。
抽象工厂:侧重于多个系列产品的构造过程。立即返回对象
2)组合模式: 通常使用生成器模式生成的。
5、抽象工厂模式
(1)功能
一种为访问类提供一个创建一组相关或相互依赖对象的接口,且访问类无须指定所要产品的具体类就能得到同族的不同等级的产品的模式结构。
(2)适用性
1)当一个系统应该独立于它的产品创建,构成、表示时
2)一个系统中存在多个产品系列并由其中的一个配置时
(3)结构
(4)参与者
。抽象工厂(AbstractFactory):提供了创建产品的接口,它包含多个创建产品的方法 newProduct(),可以创建多个不同等级的产品。
具体工厂(ConcreteFactory):主要是实现抽象工厂中的多个抽象方法,完成具体产品的创建。
抽象产品(Product):定义了产品的规范,描述了产品的主要特性和功能,抽象工厂模式有多个抽象产品。
具体产品(ConcreteProduct):实现了抽象产品角色所定义的接口,由具体工厂来创建,它 同具体工厂之间是多对一的关系。
(5)优缺点
1)优点:
a.易于交换产品系列,通过改变具体工厂类就可以使用不同的产品配置
b.具体实例的创建过程与客户端分离,产品的具体类与客户端分离。
- 可以在类的内部对产品族中相关联的多等级产品共同管理,而不必专门引入多个新的类来进行管理。
- 当增加一个新的产品族时不需要修改原代码,满足开闭原则
2)缺点:
a.新增产品系列和产品类型的需要增加产品类,具体工厂类等,需要增加很多的类(不满足封闭开放原则)
b.需要依赖客户端决定实例化哪一个具体工厂类
c.当产品族中需要增加一个新的产品时,所有的工厂类都需要进行修改。
(6)使用抽象工厂模式一般要满足以下条件
- 系统中有多个产品族,每个具体工厂创建同一族但属于不同等级结构的产品。
- 系统一次只可能消费其中某一族产品,即同族的产品一起使用。
(7)相关模式
1)工厂方法模式:抽象工厂通常使用工厂方法实现。抽象工厂模式是工厂方法模式的升级版本,工厂方法模式只生产一个等级的产品,而抽象工厂模式可生产多个等级的产品。
2)原型模式:抽象工厂也可以使用原型模式创建
3)单例模式:一个具体的工厂通常是一个单例
6、单例模式
(1)功能
单例模式:保证一个类仅有一个实例,并提供一个访问它的全局访问点。
(2)适用性
1)一个类只有一个实例的时候而且可以从一个众所周知的访问点访问它时
(3)结构
(4)参与者
(5)优缺点
(6)相关模式
1)抽象工厂:可以使用单例模式实现
2)生成器模式:可以使用单例模式实现
3)原型模式:可以使用单例模式实现
(7)四种写法:
1)饿汉式:
public class Singleton1 { private Singleton1() {};//私有的无参构造器 private static Singleton1 instance = new Singleton1(); private static Singleton1 getInstance(){ return instance; } }
2)volatile关键字+双重检查锁定
public class Singleton2 { private Singleton2(){}; private volatile static Singleton2 instance;//加上volite防止指令重排和寄存器将数据缓存 private static Singleton2 getInstance(){ if (instance == null) { synchronized(Singleton2.class){//加锁防止多线程生成多个实例 if (instance == null) { instance = new Singleton2();//指令重排序,先完成赋值,但构造函数还没执行完 } } } return instance; } }
3)静态内部类(预初始化单例模式 pre-initiallization of singletons)
public class Singleton3 { private Singleton3(){}; private static Singleton3 getInstance(){ return Holder.instance; } private static class Holder{ private static Singleton3 instance = new Singleton3(); } }
缺点:
I: 如果程序执行过程中,某个单例一直不需要创建,此时在程序启动过程中初始化该单例,只能是浪费资源
II:pre-initiallization 迫使使用单例的应用程序组件在其实现中暴露单例信息,以便能够明确地初始化单例,这可能破坏封装。4)枚举
public enum Singleton4 { INSTANCE; private Singleton4(){}; }
五、总结
1、各个创建型模式对比
创建型模式 |
特性 |
参与者 |
适用范围 |
实现 |
简单工厂模式 |
包含一个产品类的层次结构和一个单独的封装了根据客户端选择动态实例化的工厂类 | 一个抽象产品多个具体产品构成的产品结构 一个工厂类(创建实例方法内包含了根据客户端选择的逻辑判断) |
违背 开放封闭原则 | |
工厂方法模式 |
产品类与工厂类层次平行 | 一个抽象的产品接口、多个具体产品组成的产品结构 一个抽象的工厂方法接口,多个具体工厂方法组成的工厂结构 一个具体工厂负责创建一个具体产品的实例 |
1)当一个类希望由他的子类来指定它所创建的对象的时候(即希望实例化的操作推迟到子类中) 2)当一个类不知道它所必选创建的对象的类的时候 |
|
抽象工厂模式 |
工厂方法模式在一系列产品下的扩展。存在多个产品类层次 | 多个产品组织结构构成的多个系列产品结构 一个抽象工厂接口,多个具体工厂构成的抽象工厂结构 一个具体工厂负责创建多个具体产品的实例 |
侧重多个系列产品的构造过程 1)当一个系统应该独立于它的产品创建,构成、表示时 2)一个系统中存在多个产品系列并由其中的一个配置时 |
1、工厂方法模式 2、原型模式 3、单例模式 |
单例模式 |
一个类有且只有一个实例 | 一个类 只有一个实例 |
一个类只有一个实例的时候而且可以从一个众所周知的访问点访问它时 | 1、volatile关键字+双检测锁定模式 2、类初始化模式(静态初始化模式) 3、加锁模式synchronize或者Lock |
原型模式 |
使用克隆复制对象而不是创建重新初始化对象 动态的设置对象运行时状态 |
只有一个抽象产品接口和多个具体产品接口 工厂类和产品类合二为一,创建产品实例的方法由工厂类内转移值产品类内。 |
1)当一个系统应该独立于它的产品创建,构成、表示时,使用原型模式 2)当要实例化的类时运行时刻指定时 3)为了避免创建一个和产品类层次平行的工厂类层次的时候 4)当一个类的实例只能有几个不同的状态组合中的一种时。 |
1、浅复制 2、深复制 |
生成器模式 |
变化部分:对象的表示部分 要与产品的构造过程分离解耦(Builder,ConcreteBuilder) 不变的部分:复杂对象的构建过程 使用单独的类封装产品的构造过程(Direct) 需要一个类来连接两个部分(变化的部分和不变的部分)这个类就是指挥者类 |
一个产品类 一个抽象的产品表示类,多个具体的产品表示类 (维护一个产品类引用,还有返回这个引用的方法) 一个指挥者封装了产品的构造过程 (构造方法传入产品表示类) |
侧重于复杂对象的构造过程 建造者模式使当创建复杂对象的算法应该独立于对象的组成部分以及它们的装配方式时适用的模式 1)对象复杂的构建过程应该独立于对象的表示时候 2)构造过程允许构造对象有不同的表示时 |
2、各个创建型模式之间的逻辑关系
工厂方法模式 将一个系列产品扩充至多个系列产品 就是 抽象工厂模式
工厂方法模式 将产品结构和工厂类结构合并,创建产品实例的方法从工厂类转移至产品类内,并由重新初始化创建实例改为克隆对象
抽象工厂模式 可以由原型模式实现,抽象工厂模式的一个具体工厂就是一个单例模式
3、模式之间的转换关系