深入理解创建类设计模式(Creational Patterns)
Posted 前端大联盟
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了深入理解创建类设计模式(Creational Patterns)相关的知识,希望对你有一定的参考价值。
茫茫人海中与你相遇
相信未来的你不会很差
来源:https://www.jianshu.com/p/5548be3766a9
创建类设计模式
-
工厂方法(Factory Method) -
抽象工厂(Abstract Factory) -
建造器 (Builder) -
原型模式 (Prototype) -
单例模式(Singleton)
一、实例创建抽象
new
关键字,
Class
对象的
newInstance
方法,以及
Constructor<T>
类的
newInstance
方法。无论利用什么样的设计模式创建对象,最终总要落入这三个范畴。
new
出
来,相反,我们使用一种被称作依赖反转的技术,用构造器参数或
setter
来配置。
二、工厂方法设计模式
定义一个接口来创建对象, 但让子类型来决定实例化什么类型。工厂方法允许一个类推迟实例化子类型。
又名抽象构造器
interface
,而是对应着接口方法。实际上,严格追究起来,Java中的
interface
本质上定义的,就是一个接口集合,或者说一个接口类,而不是一个单个的接口。为了更方便地阐述它的原理,这里我们定义了一个
abstract class
,而不是传统阐释工厂方法时常用的
interface。
interface Product{}
class ProductA implements Product{}
abstract class Foo{
Product product;
// 获得一个配置好的默认Product,如果不存在,则创建并初始化它。
Product getConfiguredProductNotNull(){
if(product == null){
this.product = new ProductA();
}
// 在经过一系列对product的配置以后
return product;
}
}
getConfiguredProductNotNull
方法不仅要配置product,而且有可能要创建一个Product实例。上面的代码采用硬编码的形式,将
ProductA
直接赋值给
this.product
。
Foo
是抽象的,它必须经过继承才能够使用。如果有任何一个子类不满意
ProductA
,我们就要重写
Foo
类。然而,重新硬编码一个新的
Product
子类只是拆东墙补西墙罢了,原有的子类可能需要继续依赖
ProductA
,也就是说
ProductA
必须存在。
interface Product{}
class ProductA implements Product{}
abstract class Foo{
Product product;
abstract protected Product createNewProduct();
// 获得一个配置好的默认Product,如果不存在,则创建并初始化它。
Product getConfiguredProductNotNull(){
if(product == null){
// 更改为调用上面的抽象方法。
this.product = createNewProduct();
}
// 在经过一系列对product的配置以后
return product;
}
}
createNewProduct
方法。注意这里的叫法,一个抽象方法广义上就是一个接口。
createNewProduct
来获得它们想要的
Product
。
// 增加ProductB的定义
class ProductB implements Product{}
// 以及两个Foo类的子类定义
class FooA extends Foo{
protected Product createNewProduct(){
return new ProductA()
}
}
class FooB extends Foo{
protected Product createNewProduct(){
return new ProductB()
}
}
Foo
类变得更加稳定了。它把创建Product的工作抽象成工厂方法,然后交由子类去实现。这个类的其它方法可以依赖工厂方法去创建一个产品,对于不同的子类会产生不同的产品,自然也就不需要修改
Foo
类了。相对的,我们的子类依然是不稳定的,所创建具体类的细节必须包含在子类中。
三、抽象工厂模式
定义一个接口来创建对象, 但让子类型来决定实例化什么类型。工厂方法允许一个类推迟实例化子类型。
protected
还是
public
的。显然,根据定义,这样的说法在正确性上没有任何问题。
interface Product{}
interface Foo{
Product createProduct();
}
Foo
这个类仅剩下一个抽象方法了。由于没有其它方法实现,我们可以把
abstract class
改成
interface
。进一步考虑这个类,我们发现,在移除了其它方法后,它的职责似乎只剩下创建
Product
这一件事情。
Foo
变成了一个抽象工厂。
Foo
只拥有一个
createProduct
方法。事实上,并没有这种奇怪的限制,
Foo
可以拥有无穷多个工厂方法。
interface ProductA{}
interface ProductB{}
interface ProductC{}
interface Foo{
ProductA createProductA();
ProductB createProductB();
ProductC createProductC();
}
ProductA,ProductB,ProductC
都变成了接口而不是具体的类。我们当然还可以继续定义这样的产品接口和对应的工厂方法,只是没必要罢了。
Product
,那么
Foo
类就是一个抽象工厂,但如果它分成了
ProductA,ProductB,ProductC
,事情就变得不一样了。在阐述这个问题之前,我们首先要看一下抽象工厂的定义。
提供一个创建一系列相关联或依赖对象的接口。
也称作 “工具箱”
interface
,这个接口类允许创建多个类的实例。根据定义,如果这些类实例是相互关联或互相依赖的,则它是抽象工厂;反之,如果它们没有任何关联,则不是抽象工厂。
附 静态工厂方法
class Person{
private Person(){};
public static Person newInstance(){
return new Person();
}
}
Foo
类型中,构造函数被设置为私有的,然后我们提供了一个
newInstance
静态方法返回这个类的实例。这样做的效果是,我们把构造器转换成了一个可具名的方法。有什么好处呢?最直观的方面是,代码的自说明性增强了。尤其是,我们可以提供多个静态工厂方法的时候,例如——
class Person{
// 职业
private String career;
private Person(){};
private Person(String career){this.career = career;}
public static Person newTeacher(){
return new Person("teacher");
}
public static Person newDoctor(){
return new Person("doctor");
}
}
四、建造器模式
分离一个复杂对象的构建过程与它的表现形式,使得一个相同的构建过程能创建出不同的表现对象。
Blacksmith a = getBlacksmithA();
Blacksmith b = getBlacksmithB();
Iron iron = new Iron();
a.put(iron).forging().get(); // 产生一个锤子
b.put(iron).forging().get(); // 产生一个镰刀
return this
;
来返回这个建造器本身以实现链式调用。在调用的最后要求使用一个结束方法,每个结束方法都会返回一个构建好的目标对象。
put
是参数配置类型的配置方法,
forging
是行为类型的配置方法,而
get
是最后的结束方法,它返回一个配置好的目标实例。
Blacksmith a = getBlacksmithA();
Iron iron = new Iron();
a.put(iron).forging(); // 此时建造器已经配置完毕。
// 要四个锤子,仅需要调用四次。
a.get();
a.get();
a.get();
a.get();
五、 原型模式
六、单例模式
单例模式是最简单的对象构建模式,前面我们在拓展静态工厂方法时曾经提到过,静态工厂方法技术可以用来实现单例模式。一个类型在全局范围内有且仅有一个实例,
class Singleton{
public static Singleton singleton = new Singleton();
private Singleton(){}
public static Singleton getSingleton(){ return this.singleton;}
}
关于这个模式的主要细节大概有三个,一是所谓的全局唯一中的全局,到底范围有多大?二是单例模式获得实例的入口必须要在它的主类吗?最后,我们将拓展性地讨论单例模式的安全性问题。
首先,所谓全局的单例中的全局,这个词在最开始仅仅指的是整个JVM运行时范围。但到今天为止,这个全局的含义已经在不停地向外扩展,我们说,现在这个单例模式中的“单例”,已经越来越趋向于“x对一关系”的意思。例如,一个应用只能拥有一个application对象,一个http请求只能拥有一个会话对象等等。在Spring中,bean对象可以是单例的,然而同一个类型的单例可以出现在两个IOC容器中。因此,在描述单例模式时,必须首先要说明它的单例范围。
此外,单例模式获得实例的入口必须在主类吗? 根据定义——
确保一个对象仅有一个实例,而且提供一个全局入口来获得这个实例。
也就是说,单例模式的入口未必需要在它的主类上。然而,在主类上定义一个静态工厂方法无疑是十分贴切的,“如果你需要一个类的实例,那就去这个类上的方法找找看”,这种想法实在是太过于自然了。
最后,我们需要探索一下单例的安全性问题。我们看到,单例的构造器被声明为私有的,以防止它被意外地在类外被创建。然而这只能确保编译期的安全,在运行期,我们可以轻松通过反射机制绕开私有,实例化一个新的单例对象。这显然与我们的初衷相违背。
此外,需要单例的通常是一个大对象,而众所周知的是,一个大对象常用懒加载模式创建,也就是说下面这种模式才是常见的模式。
class Singleton{
public static Singleton singleton = null;
private Singleton(){}
public static Singleton getSingleton(){
if(singleton == null){
singleton = new Singleton();
}
return this.singleton;
}
}
这个时候,由于访问的是一个全局单例对象,并发也需要纳入考虑范围。我们可以加锁来解决这个问题,因为单例对象的创建只有一次(或者说在某个范围内只有一次),锁带来的开销通常是可以承受的,否则,就需要考虑是否需要使用CAS同步机制。如何选择同步机制超出了本文的讨论范围。回到上面构造器私有的问题,为了确保安全,在这种模式下,我们可以在私有构造器中判断singleton
引用是否为null
,如果不为null
则抛出一个异常,用来警告客户端执行了不应该执行的操作。
但通常而言,技术是不完美的。我们可以不停地增加各种各样的约束,但最后总有一些例外来打破这些约束,这种情况下需要的是计算机技术之外的一些支持,当需要实现一个单例模式时,如果你的命名非常契合单例的命名习惯,并且详细描述了使用这个单例需要注意的情况,就能大幅度减少客户端程序员犯错的概率。
我们在虚拟的空间与你相遇,期待可以碰撞出不一样的火花
以上是关于深入理解创建类设计模式(Creational Patterns)的主要内容,如果未能解决你的问题,请参考以下文章
原型模式(Prototype)-创建型(Creational)设计模式
编程思想设计模式创建模式creational抽象工厂模式abstract_factory