快速理解设计模式之创建型模式
要想更全面理解设计模式,建议先查看Spring的设计模式快速入门干货,前半部分是设计模式的分类和综述,与Spring无关。
创建型模式
对象的创建会消耗掉系统的很多资源,所以对对象的创建进行研究,从而能够高效地创建对象就是创建型模式要探讨的问题。这里有6个具体的创建型模式可供研究,它们分别是:
- 简单工厂模式(Simple Factory)
- 工厂方法模式(Factory Method)
- 抽象工厂模式(Abstract Factory)
- 建造者模式(Builder)
- 原型模式(Prototype)
- 单例模式(Singleton)
说明:严格来说,简单工厂模式不是GoF总结出来的23种设计模式之一。 GoF在《设计模式》一书中将工厂模式分为两类:工厂方法模式与抽象工厂模式。将简单工厂模式看为工厂方法模式的一种特例,两者归为一类。
三种工厂模式的演进
我们的目标是客户提出某个要求,我们的工厂负责new出来这个产品。
简单工厂模式的做法:
- 写一个工厂类,使用静态方法,根据传参来new不同的产品对象
- 客户端调用这个工厂类的静态方法,就可以获取想要的产品
这样的缺点是新增产品后工厂的静态类的代码会逐渐繁杂,不便于维护。所以诞生了工厂模式。
工厂模式的做法:
- 写一个抽象工厂类,每个工厂实现类负责将new不同种类的产品。
- 客户端new不同的工厂实现类就可以获得不同的产品。
这样的缺点也很明显,客户需要new不同种类的产品,就先需要new对应的工厂。所以诞生了抽象工厂模式。
抽象工厂模式的做法:
- 写一个抽象工厂类,然后每个工厂实现类负责new一组不同的种类的产品。
- 客户端new需要某一组的产品,只需要new一个工厂即可。
工厂模式或者抽象工厂模式中,产品类或者工厂类实现几个接口,完全取决于业务的需要,并没有强制要求。
网上大部分博客在介绍工厂模式的时候都做了大篇幅的文字介绍和代码演示,实际上三个模式的区别并没有好好介绍清楚。所以本文一步到位的讲解三种模式区别。
建造者模式(Builder)
建造者模式:是将一个复杂的对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。
工厂类模式提供的是创建单个类的模式,而建造者模式则是将各种产品集中起来进行管理,用来创建复合对象,所谓复合对象就是指某个类具有不同的属性,
建造者模式通常包括下面几个角色:
- Builder:给出一个抽象接口,以规范产品对象的各个组成成分的建造。这个接口规定要实现复杂对象的哪些部分的创建,并不涉及具体的对象部件的创建。
- ConcreteBuilder:实现Builder接口,针对不同的商业逻辑,具体化复杂对象的各部分的创建。在建造过程完成后,提供产品的实例。
- Director:调用具体建造者来创建复杂对象的各个部分,在指导者中不涉及具体产品的信息,只负责保证对象各部分完整创建或按某种顺序创建,是一个具体的类。
- Product:要创建的复杂对象类。
一段代码就可以理解这个模式了:
public class Director {
public Product constructProduct(ConcreteBuilder concreteBuilder){
concreteBuilder.buildBasic();
concreteBuilder.buildWalls();
concreteBuilder.roofed();
return concreteBuilder.buildProduct();
}
}
原型模式(Prototype)
原型(Prototype)模式是一种对象创建型模式,他采取复制原型对象的方法来创建对象的实例。使用原型模式创建的实例,具有与原型一样的数据。
public class Prototype implements Cloneable{
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
protected Object clone() {
try {
return super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}finally {
return null;
}
}
public static void main ( String[] args){
Prototype pro = new Prototype();
Prototype pro1 = (Prototype)pro.clone();
}
}
此处使用的是浅拷贝,关于深浅拷贝,大家可以另行查找相关资料。
单例模式(Singleton)
饿汉式
在定义类的静态私有变量同时进行实例化。
public class Singleton {
private static Singleton instance = new Singleton();
private Singleton (){}
public static Singleton getInstance() {
return instance;
}
}
好处:线程安全;获取实例速度快
缺点:类加载即初始化实例,内存浪费
懒汉式
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 static class SingletonHolder {
private static final Singleton INSTANCE = new Singleton();
}
private Singleton (){}
public static final Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
}
这种方式利用了classloder的机制来保证初始化instance时只有一个线程,但singleton类被装载了,instance不一定被初始化。因为SingletonHolder类没有被主动使用,只有显示通过调用getInstance方法时,才会显示装载SingletonHolder类,从而实例化instance。如果实例化instance很消耗资源,我们想让他延迟加载,此外,我们不希望在Singleton类加载时就实例化,因为不能确保Singleton类还可能在其他的地方被主动使用从而被加载。这个时候,这种方式相比饿汉式就显得更合理。
枚举
public enum Singleton {
INSTANCE;
public void whateverMethod() {
}
}
优点:写法简单,天然线程安全,可防止反射生成实例,是Effective Java作者Josh Bloch提倡的方式。
哎呀,如果我的名片丢了。微信搜索“全菜工程师小辉”,依然可以找到我不过笔者用的最多的还是饿汉式,看不少源码也常用饿汉式,哈哈哈