一、前言
创建型模式
对类的实例化过程进行了抽象,能够将软件模块中对象的创建和对象的使用分离。
为了使软件的结构更加清晰,外界对于这些对象只需要知道它们共同的接口,而不清楚其具体的实现细节,使整个系统的设计更加符合单一职责原则。
简单来讲,相较于另外两种类型(结构型
和行为型
),创建型模式
的侧重点在于如何创建对象
本文会简单介绍几种常见的创建型模式
:
- 单例模式
- 工厂模式
- 建造者模式
注意,原型模式不包含在本文中,想要了解更多的可以自行冲浪搜索
二、创建型模式:单例模式
1、模式定义
单例模式
的目的是保证一个类仅有一个实例,并提供一个访问它的全局访问点。
单例类拥有一个私有构造函数,确保用户无法通过new
关键字直接实例化它。
除此之外,该模式中包含一个静态私有成员变量与静态公有的工厂方法。
该工厂方法负责检验实例的存在性并实例化
自己,然后存储在静态成员变量中,以确保只有一个实例
被创建。
图片来源:自己画的
2、使用方法
单例使用方法简单,理解起来也比较容易,笔者这里就直接开门见山,介绍四种常见的使用方式
2.1 饿汉式
public class Singleton1 {
private static final Singleton1 instance = new Singleton1();
public Singleton1() {
}
public static Singleton1 getInstance() {
return instance;
}
public void doSomething() {
//doSomething
}
}
复制代码
饿汉式
是常见使用方法中最简单的一种,JVM类加载机制
得以保证类加载过程中是线程安全的,所以多线程环境下使用也是没问题的
饿汉式
单例模式唯一的问题可能就是:并非懒加载,只要单例类被虚拟机加载,就必然会创建instance
实例。此时,若是被提前初始化的示例工程中用不到,那的确是白白消耗了初始化的时间和内存空间
不过考虑到单例模式
的使用场景,笔者认为大多数情况下使用饿汉式
并不会给项目带来多余的负担。
2.2 懒汉式:静态内部类
public class Singleton2 {
private Singleton2() {
}
public static Singleton2 getInstance() {
return SingletonHolder.instance;
}
private static class SingletonHolder {
private static final Singleton2 instance = new Singleton2();
}
public void doSomething() {
//doSomething
}
}
复制代码
和2.1
的代码示例相同,静态内部类同样由JVM类加载机制
保证了多线程环境下的安全性;同时,由于没有在局部变量中声明,所以就算因为非主观因素导致单例类被虚拟机加载,也无需担心创建了暂时用不到的对象导致占用内存。
静态内部类
的单例模式既能保证多线程环境下的安全又实现了懒加载,代码写起来比较方便,这也是笔者个人比较喜欢用的一种方式
2.3 懒汉式:双重校验锁DCL
public class Singleton3 {
private volatile static Singleton3 instance;
private Singleton3() {
}
public static Singleton3 getInstance() {
if (instance == null) {
synchronized (Singleton3.class) {
if (instance == null) {
instance = new Singleton3();
}
}
}
return instance;
}
public void doSomething() {
//doSomething
}
}
复制代码
DCL(DoubleCheckLock)双重校验锁
可能是所有单例模式
中传播最广的方式了,这也是笔者在工作早期接触最多的单例模式;DCL双重检验锁
的方式把线程安全和懒加载都考虑到了,并且涉及到Java并发编程
中两个比较重要的关键字:volatile
和synchronized
,相较于上面的两种示例,DCL
可以拎出来的点比较多,笔者猜测可能这也是为什么DCL
传播这么广的原因
在实际的项目开发中如果不觉得使用步骤较为繁琐,代码量可以接受的话,DCL
也是比较推荐的一种使用方式
注:关于DCL双重校验锁
要不要加volatile
关键字取决于Java版本,在jdk8
中已经确保new、初始化为原子性操作,不会出现JIT导致指令重排的情况,想了解更多的朋友请点击这里
2.4 枚举类
public enum Singleton4 {
getInstance();
public void doSomething() {
//doSomething
}
}
复制代码
Java1.5以后,单例模式
的实现方式终于迎来的新面孔:枚举类单例,这也是《Effective Java》作者Joshua 力荐的一种使用方式
枚举类
是没有构造方法的,枚举类
的创建完全由JVM
内部实现,不对外开放。这样的特性也使得我们不能使用new
的方式来创建枚举对象,对应的缺点就是枚举类
的单例模式
不是懒加载的
枚举类
单例除了能保证线程安全外,还加入了新功能:防止单例模式被破坏,原因有两点:
-
枚举类
无法被反射创建强行使用反射创建会收到错误:java.lang.IllegalArgumentException: Cannot reflectively create enum objects
-
枚举类
不能被反序列化序列化的时候,仅仅是将枚举对象的name属性输出到结果中,反序列化的时候则是通过java.lang.Enum的valueOf方法来根据名字查找枚举对象。
同时,编译器是不允许任何对这种序列化机制的定制的,因此禁用了writeObject、readObject、readObjectNoData、writeReplace和readResolve等方法。
最后,关于枚举类
还有一点需要注意:枚举类
不能继承任何类,枚举类
在编译后会继承自java.lang.Enum,由于Java单继承
的特性导致枚举类不能再继承任何其他类,但是枚举类
可以实现接口
3、破坏单例模式及防止破坏单例
正如2.4
小节中提到的,除了枚举类单例
外,其他的三种单例模式
都可以通过使用反射来破坏在进程中的唯一性
单例类
由于自身的特殊性导致无法被反射
和反序列化
创建,所以对象的唯一性暂时无法被破坏,但是可以修改枚举类的值
笔者这里写了一个简单的示例代码来破坏单例,感兴趣的朋友也可以自己在单元测试中试一试
@Test
public void main() {
testSingleton1();
testSingleton2();
testSingleton3();
}
/*1.恶汉模式*/
private static void testSingleton1() {
Object fromInstance = Singleton1.getInstance();
Object fromReflect = createClassWithReflect(Singleton1.class);
System.out.println(fromInstance.equals(fromReflect));
}
/*2.懒汉模式-静态内部类*/
private static void testSingleton2() {
Object fromInstance = Singleton2.getInstance();
Object fromReflect = createClassWithReflect(Singleton2.class);
System.out.println(fromInstance.equals(fromReflect));
}
/*3.懒汉模式-DoubleCheckLock*/
private static void testSingleton3() {
Object fromInstance = Singleton3.getInstance();
Object fromReflect = createClassWithReflect(Singleton3.class);
System.out.println(fromInstance.equals(fromReflect));
}
private static <T> T createClassWithReflect(Class<T> clz) {
Constructor<?> constructor = clz.getDeclaredConstructor();
constructor.setAccessible(true);
return (T) constructor.newInstance();
}
复制代码
打印结果
false
false
false
复制代码
从打印结果可以看出,除枚举类
外的三种实现方式全部沦陷,都可以通过反射
来创建不同的实例对象来破坏单例模式
的唯一性
反射
创建枚举类
会受到报错信息:java.lang.IllegalArgumentException: Cannot reflectively create enum objects,笔者这里就不展示了,感兴趣的同学可以自己动手试一试
最后提一句,网上其他文章提到了使用clone
方法来破坏单例,笔者认为这点不成立,因为不重写clone()
方法并且不将访问修饰符改为public
,使用者是无法调用clone
方法来创建新的实例对象
的
4、小结
至此,几种常见的单例模式
实现方式都介绍完了,简单总结下:上述几种实现方式都可以在保证多线程环境下安全性;除了枚举单例
之外,其他几种方式的单例模式
都可以被破坏
此小节涉及到的代码在这里
三、创建型模式:工厂方法
1、模式定义
工厂方法
模式又称为工厂模式,在工厂方法
模式中,工厂父类负责定义创建产品对象的公共接口,而工厂子类则负责生成具体的产品对象,这样做的目的是将产品类的实例化
操作延迟到工厂子类中完成,即通过工厂子类来确定究竟应该实例化哪一个具体产品类。
工厂方法模式包含四个角色:
抽象产品
是定义产品的接口,是工厂方法模式所创建对象的超类型,即产品对象的共同父类或接口;具体产品
实现了抽象产品接口,某种类型的具体产品由专门的具体工厂创建,它们之间往往一一对应;抽象工厂
中声明了工厂方法,用于返回一个产品,它是工厂方法模式的核心,任何在模式中创建对象的工厂类都必须实现该接口;具体工厂
是抽象工厂类的子类,实现了抽象工厂中定义的工厂方法,并可由客户调用,返回一个具体产品类的实例。
图片来源:自己画的
2、代码示例
角色1:抽象产品类
public abstract class AbstractProduct {
public abstract String getName();
}
复制代码
角色2:产品实现类
public class ProductA extends AbstractProduct {
@Override
public String getName() {
return "A";
}
}
public class ProductB extends AbstractProduct {
@Override
public String getName() {
return "B";
}
}
复制代码
角色3:抽象工厂类
public abstract class AbstractFactory {
public abstract AbstractProduct createProduct();
}
复制代码
角色4:工厂实现类
public class ProductAFactory extends AbstractFactory {
@Override
public AbstractProduct createProduct() {
return new ProductA();
}
}
public class ProductBFactory extends AbstractFactory {
@Override
public AbstractProduct createProduct() {
return new ProductB();
}
}
复制代码
工厂方法使用示例
@Test
public void main() {
AbstractFactory factoryA = new ProductAFactory();
AbstractFactory factoryB = new ProductBFactory();
AbstractProduct productA = factoryA.createProduct();
AbstractProduct productB = factoryB.createProduct();
System.out.println("工厂A生产的产品名称:" + productA.getName());
System.out.println("工厂B生产的产品名称:" + productB.getName());
}
复制代码
打印结果
工厂A生产的产品名称:A
工厂B生产的产品名称:B
复制代码
3、源码锚点
从本小节开始,笔者将会对每种设计模式都以Java/Android源码举例说明,用锚点记忆法
来辅助记忆;比如提起建造者模式
就像到Android AlertDialog
,提到观察者模式
就想到OnClickListener
一样
工厂模式
在源码中的提现,我们可以记住Java容器类
的迭代器:Iterator
为了加深记忆,我们这里简单剖析使用工厂方法迭代器
的角色分工:
- 抽象工厂
- Iterable
- 工厂实现
- ArrayList
- HashMap
- 抽象产品
- Iterator
- 产品实现(jdk1.8)
- ArrayList中的Itr类
- HashMap中的EntryIterator/KeyIterator/ValueIterator类
从上面的结构来看,ArrayList
和HashMap
中的iterator
方法其实就相当于一个工厂方法
,专为new
对象而生,这里iterator
方法是构造并返回一个具体的迭代器
4、小结
工厂方法
模式的主要优点是增加新的产品类时无须修改现有系统,并封装了产品对象的创建细节,系统具有良好的灵活性和可扩展性;其缺点在于增加新产品的同时需要增加新的工厂,导致系统类的个数成对增加,在一定程度上增加了系统的复杂性
笔者这里没有提到简单工厂
,实际上,当把工厂方法
的抽象工厂类删除掉,就是简单工厂
模式了,所以可以把简单工厂
理解为工厂方法
的简化版本;笔者这里没有给出示例,想要了解更多的可以去看阿里巴巴淘系技术
在知乎上的回答:简单工厂模式、工厂方法模式和抽象工厂模式有何区别
此小节涉及到的代码在这里
四、创建型模式:抽象工厂
1、模式定义
抽象工厂
模式是所有形式的工厂模式中最为抽象和最具一般性的一种形态。抽象工厂
模式与工厂方法
模式最大的区别在于,工厂方法
模式针对的是一个产品等级结构,而抽象工厂
模式则需要面对多个产品等级结构
抽象工厂
模式同样包含四个角色:
抽象工厂
用于声明生成抽象产品的方法;具体工厂
实现了抽象工厂声明的生成抽象产品的方法,生成一组具体产品,这些产品构成了一个产品族,每一个产品都位于某个产品等级结构中;抽象产品
为每种产品声明接口,在抽象产品中定义了产品的抽象业务方法;具体产品
定义具体工厂生产的具体产品对象,实现抽象产品接口中定义的业务方法。
虽然抽象工厂
的角色数量和工厂方法
相同,都是4个,但由于每个角色下还有其他角色,理解起来就会稍微有点费劲,这一点也可以从抽象工厂
和工厂方法
的UML类图
看出来区别,抽象工厂
明显更复杂一些
由于抽象工厂
稍微有点复杂,不能再用上面的ProductA、FactoryB等无实际意义的类名来举例,为了方便理解,笔者这里使用手机组装厂
来做类比,他们的角色分工如下:
- 抽象产品
- 电池组装厂
- 屏幕组装厂
- 具体产品实现
- 电池
- 比亚迪电池供应商
- 德赛电池供应商
- 屏幕
- 京东方屏幕供应商
- LG屏幕供应商
- 电池
- 抽象工厂
- 手机组装厂
- 具体工厂实现
- 华为P50的组装厂
- 小米10的组装厂
有了上面的角色分工结构,在下面代码示例我们就直接对号入座,这样的方式可能会方便理解
图片来源:自己画的
2、代码示例
角色1-1:抽象产品类-电池
public abstract class AbstractProductBattery {
public AbstractProductBattery() {
createBattery();
}
public abstract String getBatteryName();
protected abstract void createBattery();
}
复制代码
角色1-2:抽象产品类-屏幕
public abstract class AbstractProductScreen {
public AbstractProductScreen() {
createScreen();
}
public abstract String getScreenName();
protected abstract void createScreen();
}
复制代码
角色2-1-1:产品实现类-电池-比亚迪
public class BYDBattery extends AbstractProductBattery {
@Override
public String getBatteryName() {
return "比亚迪(BYD)";
}
@Override
protected void createBattery() {
System.out.println("加班加点生产比亚迪电池中...");
}
}
复制代码
角色2-1-2:产品实现类-电池-德赛
public class DesayBattery extends AbstractProductBattery {
@Override
public String getBatteryName() {
return "德赛(Desay)";
}
@Override
protected void createBattery() {
System.out.println("加班加点生产德赛电池中...");
}
}
复制代码
角色2-2-1:产品实现类-屏幕-京东方
public class BOEScreen extends AbstractProductScreen {
@Override
public String getScreenName() {
return "京东方(BOE)";
}
@Override
protected void createScreen() {
System.out.println("加班加点生产京东方屏幕中...");
}
}
复制代码
角色2-2-2:产品实现类-屏幕-LG
public class LGScreen extends AbstractProductScreen {
@Override
public String getScreenName() {
return "LG";
}
@Override
protected void createScreen() {
System.out.println("加班加点生产LG屏幕中...");
}
}
复制代码
角色3:抽象工厂类
public abstract class AbstractPhoneFactory {
protected String brand;//工厂生产的手机品牌
protected String model;//工厂生产的手机型号
protected AbstractProductScreen phoneScreen;//手机使用的屏幕
protected AbstractProductBattery phoneBattery;//使用的电池
public AbstractPhoneFactory(String brand, String model) {
this.brand = brand;
this.model = model;
this.phoneScreen = createPhoneScreen();
this.phoneBattery = createPhoneBattery();
}
public String getBrand() {
return brand;
}
public String getModel() {
return model;
}
public AbstractProductScreen getPhoneScreen() {
return phoneScreen;
}
public AbstractProductBattery getPhoneBattery() {
return phoneBattery;
}
protected abstract AbstractProductScreen createPhoneScreen();
protected abstract AbstractProductBattery createPhoneBattery();
}
复制代码
角色4-1:工厂实现类-华为
public class HuaWeiPhoneFactory extends AbstractPhoneFactory {
public HuaWeiPhoneFactory() {
super("华为(HuaWei)", "P50");
}
@Override
protected AbstractProductScreen createPhoneScreen() {
return new LGScreen();//Lg屏幕
}
@Override
protected AbstractProductBattery createPhoneBattery() {
return new DesayBattery();//德赛的电池
}
}
复制代码
角色4-2:工厂实现类-小米
public class XiaoMiPhoneFactory extends AbstractPhoneFactory {
public XiaoMiPhoneFactory() {
super("小米(XiaoMi)", "10");
}
@Override
protected AbstractProductScreen createPhoneScreen() {
return new BOEScreen();//京东方的屏幕
}
@Override
protected AbstractProductBattery createPhoneBattery() {
return new DesayBattery();//德赛电池
}
}
复制代码
抽象工厂使用示例
public void main() {
AbstractPhoneFactory phoneFactory1 = new HuaWeiPhoneFactory();
AbstractPhoneFactory phoneFactory2 = new XiaoMiPhoneFactory();
print(phoneFactory1);
print(phoneFactory2);
}
private void print(AbstractPhoneFactory phoneFactory) {
System.out.println("产线品牌:" + phoneFactory.getBrand()
+ ",生产型号:" + phoneFactory.getModel()
+ ",电池厂商:" + phoneFactory.getPhoneBattery().getBatteryName()
+ ",屏幕厂商:" + phoneFactory.getPhoneScreen().getScreenName());
}
复制代码
打印结果
产线品牌:华为(HuaWei),生产型号:P50,电池厂商:德赛(Desay),屏幕厂商:LG
产线品牌:小米(XiaoMi),生产型号:10,电池厂商:德赛(Desay),屏幕厂商:京东方(BOE)
复制代码
从打印结果可以看到,小米和华为都是用德赛的电池,但屏幕供应商用的不是同一家
若我们想增加一个产品线,只需要想小米和华为工厂一样,继承/实现AbstractPhoneFactory
抽象工厂类即可,每个零件来自哪个供应商可以随便选择;相同的,我们也可以增加供应商来丰富我们的产品,这就是抽象工厂
模式带来的好处
3、源码锚点
抽象工厂
方法模式在Android源码中的实现相对来说是比较少的,在《Android 源码设计模式解析与实战》
一书中提到是Android底层对MediaPlayer
的创建是可以看作为抽象工厂
,这一块代码笔者不熟悉,为了防止误人子弟,抽象工厂
模式的源码这里笔者就不再举例,对抽象工厂
源码体现感兴趣的可以自行查找其他资料
4、小结
抽象工厂
模式的主要优点是隔离了具体类的生成,使得客户并不需要知道什么被创建,而且每次可以通过具体工厂类创建一个产品族中的多个对象,增加或者替换产品族比较方便,增加新的具体工厂和产品族很方便;主要缺点在于增加新的产品等级结构很复杂,需要修改抽象工厂和所有的具体工厂类,对“开闭原则”的支持呈现倾斜性。
笔者个人总结的抽象工厂
和工厂方法
不同点:抽象工厂
模式拥有多个抽象产品
类,也就是本示例的电池抽象类和屏幕抽象类
抽象工厂
模式适用情况包括:一个系统不应当依赖于产品类实例如何被创建、组合和表达的细节;系统中有多于一个的产品族,而每次只使用其中某一产品族;属于同一个产品族的产品将在一起使用;系统提供一个产品类的库,所有的产品以同样的接口出现,从而使客户端不依赖于具体实现。
此小节涉及到的代码在这里
五、创建型模式:建造者模式
1、模式定义
建造者模式
将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。建造者模式
是一步一步创建一个复杂的对象,它允许用户只通过指定复杂对象的类型和内容就可以构建它们,用户不需要知道内部的具体构建细节
下面我们以Android中创建一个弹窗类来举例,该类有以下几个特性:
- 弹窗类有左右两个按钮,标题和内容共四个属性,弹窗类提供
Builder类
来组装自己 - 一旦弹窗被创建,左右两个按钮文字不希望被更改,也就说
Dialog
本身不提供修改左右按钮的方法 Builder
提供不同的创建方法,比如:创建左右两个按钮的弹窗、只有确认按钮的弹窗,创建过程由Builder来管理
建造者模式理解起来也比较容易,我们接下来康康5.2
中的代码示例
2、代码示例
public class CommonDialog {
private Params mParams;
private CommonDialog(Params params) {
this.mParams = params;
}
public void setTitleText(String title) {
mParams.titleText = title;
}
public void setMessageText(String message) {
mParams.messageText = message;
}
public void show() {
//set view...
}
public static class Builder {
protected Params mParams = new Params();
public Builder setTitleText(String title) {
mParams.titleText = title;
return this;
}
public Builder setMessageText(String message) {
mParams.messageText = message;
return this;
}
public Builder setConfirmText(String confirm) {
mParams.confirmText = confirm;
return this;
}
public Builder setCancelText(String cancel) {
mParams.cancelText = cancel;
return this;
}
public CommonDialog create() {
//create normal dialog logic
return new CommonDialog(mParams);
}
public CommonDialog createOnlyConfirm() {
//create only have confirm btn dialog logic
mParams.cancelText = null;
return new CommonDialog(mParams);
}
}
private static class Params {
/*public to anyone*/
private String titleText;
private String messageText;
/*private field , runtime not change*/
private String confirmText;
private String cancelText;
}
}
复制代码
建造者模式使用示例
public void main() {
CommonDialog.Builder builder = new CommonDialog.Builder();
builder.setMessageText("will you marry me");
builder.setConfirmText("yes");
builder.setCancelText("no");
CommonDialog normalDialog = builder.create();//创建普通对话框
normalDialog.show();
builder.setMessageText("are you free now?");
builder.setConfirmText("yes");
CommonDialog onlyConfirmDialog = builder.createOnlyConfirm();//创建只有确认按钮的对话框
onlyConfirmDialog.show();
onlyConfirmDialog.setMessageText("Let's go to the movies?");
onlyConfirmDialog.show();
}
复制代码
看使用示例就能明白,当在创建Dialog
过程中,可以随意的更改任何属性;一旦创建了Dialog
实例对象,可修改的属性就不多了。基于此,笔者总结一下Builder
模式两大特点:限制、封装
3、源码锚点
建造者模式在Android源码中的实现:AlertDialog
4、小结
建造者模式
的主要优点在于客户端不必知道产品内部组成的细节,将产品本身与产品的创建过程解耦,使得相同的创建过程可以创建不同的产品对象,每一个具体建造者都相对独立,而与其他的具体建造者无关,因此可以很方便地替换具体建造者或增加新的具体建造者,符合“开闭原则
”,还可以更加精细地控制产品的创建过程
当然,建造者模式
也是有缺点的,其主要缺点在于由于建造者模式
所创建的产品一般具有较多的共同点,其组成部分相似,因此其使用范围受到一定的限制,相对应的代码量也会增加不少;如果产品的内部变化复杂,可能会导致需要定义很多具体建造者
类来实现这种变化,导致系统变得很庞大。
建造者模式
适用情况包括:
- 需要生成的产品对象有复杂的内部结构,这些产品对象通常包含多个成员属性;
- 需要生成的产品对象的属性相互依赖,需要指定其生成顺序;
- 对象的创建过程独立于创建该对象的类;
- 隔离复杂对象的创建和使用,并使得相同的创建过程可以创建不同类型的产品。
此小节涉及到的代码在这里
六、总结
本文介绍了常见的几种创建型设计模式
,简单总结一下:
单例模式
:保证一个类仅有一个实例,常见实现方式有- 饿汉式:若被提前加载会占用内存
- 懒汉式-静态内部类
- 懒汉式-双重校验锁DCL
- 枚举类单例:由虚拟机初始化,防止破坏唯一性
工厂方法模式
,通过工厂子类来确定究竟应该实例化哪一个具体产品类- 简单工厂:工厂方法的简化模式
抽象工厂模式
:提供一个创建一系列相关或相互依赖对象的接口,而无须指定它们具体的类建造者模式
:将复杂对象的构造与它的表示分开,这样可以在相同的构造过程中创建不同的表示形式。
由于笔者水平有限,文中难免会出现遗漏甚至错误的地方,若您发现任何问题或者有任何建议请在这里提交Issue,感谢
全文完