文章目录
设计模式——创建型模式
单例模式
概念:保证一个类仅有一个实例,并提供一个访问它的全局访问点
实现一:饿汉模式
饿汉在类加载时就完成了初始化,避免了多线程同步的问题,如果始终没有使用这个实例,就会造成内存浪费。
实现二:懒汉模式(线程不安全)
懒汉模式声明了一个静态对象,在用户第一次调用时初始化。在多线程时不能正常工作。
实现三:懒汉模式(线程安全)
这种写法在多线程中可以很好的工作,但是每次调用getInstance方法都需要同步,造成不必要的时间开销。
实现四:双重检查模式(DCL)
在方法内部进行了两次判空,第一次为了不必要的同步,第二次是在实例等于null的时候才创建,但是这样会出现DCL问题,如下图
正常情况下:
指令重排:
创建对象时会出现指令重排。
指令重排序可以保证串行语义一致,但是没有义务保证多线程间的语义也一致。也就是说上面3行伪代码的2和3之间虽然被重排序了,但是是不影响串行语义的。但是在多线程并发执行的情况就可能出现:
这样就很好想了:
当一个线程创建对象出现了指令重排,他执行了步骤1、3,此时另一个线程过来取单例去使用,但是这时对象创建的初始化工作还没有做,所有就会出现问题。
解决:使用volatile修饰对象
实现五:静态内部单例模式
第一次加载Singleton时,并不会初始化sInstance,只有在第一次调用getInstance方法时虚拟机加载SingletonHolder并初始化SInstance。这样保证了线程安全,也能保证Singleton类的唯一性。
实现六:枚举单例
默认枚举单例创建是线程安全的。并且在任何情况先都是单例。
上面的单例模式中有一种情况会重新创建对象——反序列化
将一个单例实例对象写到磁盘再读回来,从而获得了一个实例。
反序列化操作提供了readResolve方法,这个方法可以让开发人员控制对象的反序列化。在上述几个方法示例中,如果要杜绝单例对象被反序列化时重新生成对象。就需要加入如上代码。
实现七:容器实现
class SingletonManager {
private static Map<String, Object> objectMap = new HashMap<>();
private SingletonManager() {}
public static void registerService(String key, Object instance) {
if (!objectMap.containsKey(key)) {
objectMap.put(key, instance);
}
}
public static Object getService(String key) {
return objectMap.get(key);
}
}
将多种单例类注入到一个统一的管理类中,在使用是通过key获取对应类型的对象。
使用场景
在一个系统中,要求一个类有且仅有一个对象,它的具体使用场景如下:
- 整个项目需要一个共享访问点或共享数据。
- 创建一个对象需要耗费的资源过多,比如访问I/O或者数据库等资源。
- 工具类对象。
简单工厂模式
又叫静态工厂方法模式。
简单工厂模式,其属于创建型设计模式,但是并不属于 23种GoF设计模式之一。提到它是为了让大家能够更好地理解后面讲到的工厂方法模式。
在简单工厂模式中有如下角色。
- Factory:工厂类,这是简单工厂模式的核心,它负责实现创建所有实例的内部逻辑。工厂类的创建产品类的方法可以被外界直接调用,创建所需的产品对象。
- IProduct:抽象产品类,这是简单工厂模式所创建的所有对象的父类,它负责描述所有实例所共有的公共接口。
- Product:具体产品类,这是简单工厂模式的创建目标。
简单实现
我们用生产计算机来举例,假设有一个计算机的代工生产商,它目前已经可以代工生产联想计算机了。随着业务的拓展,这个代工生产商还要生产惠普和华硕的计算机。这样我们就需要用一个单独的类来专门生产计算机,这就用到了简单工厂模式。下面我们来实现简单工厂模式。
抽象产品类Computer
具体产品类
创建各个品牌的计算机,其都继承了自己的父类Computer,并实现了父类的start方法。具体的计算机产品分别是联想、惠普和华硕计算机:
工厂类
创建一个工厂类,它提供了一个静态方法 createComputer 用来生产计算机。你只需要传入自己想生产的计算机的品牌,它就会实例化相应品牌的计算机对象,代码如下所示:
客户端调用
客户端调用工厂类,传入"hp"生产出惠普计算机并调用该计算机对象的 start 方法
使用场景及优缺点
使用场景
- 工厂类负责创建的对象比较少。
- 客户只需知道传入工厂类的参数,而无须关心创建对象的逻辑。
优点
- 使用户根据参数获得对应的类实例,避免了直接实例化类,降低了耦合性。
缺点
- 可实例化的类型在编译期间已经被确定。如果增加新类型,则需要修改工厂,这违背了开放封闭原则。简单工厂需要知道所有要生成的类型,其当子类过多或者子类层次过多时不适合使用。
工厂方法模式
定义一个用于创建对象的接口,让子类决定实例化哪个类。工厂方法使一个类的实例化延迟到其子类。
工厂方法模式中有以下角色:
- Product:抽象产品类。
- ConcreteProduct:具体产品类,实现Product接口。
- Factory:抽象工厂类,该方法返回一个Product类型的对象。
- ConcreteFactory:具体工厂类,返回ConcreteProduct实例。
简单实现
工厂方法模式的抽象产品类与具体产品类的创建和简单工厂模式是一样的,具体在上面简单工厂。
创建抽象工厂:
抽象工厂里面有一个 createComputer 方法,想生产哪个品牌的计算机就生产哪个品牌的。
具体工厂
广达代工厂是一个具体的工厂,其继承抽象工厂,通过反射来生产不同厂家的计算机:
客户端调用
客户端创建了GDComputerFactor,并分别生产了联想、惠普和华硕计算机:
使用场景
在任何需要生成复杂对象的地方,都可以使用工厂方法模式。复杂对象适合使用工厂模式,用new就可以完成创建的对象无需使用工厂模式。
简单工厂和工厂方法
对于简单工厂,我们在工厂类中进行了必要的逻辑判断,根据不同的条件实例化相关的类。
除去了客户端与具体产品的依赖,但是带来了一个问题:如果我们需要添加产品就需要修改我们的工厂类,这违背了开放封闭原则。
而工厂方法模式就没有违背这个开放封闭原则。如果我们需要生产苹果计算机,则无须修改工厂类,直接创建产品即可。
建造者模式
将一个复杂的对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。
它允许用户不知道内部构建细节的情况下,可以精细的控制构造流程。
在建造者模式中有如下角色
- Director:导演类,负责安排已有模块的顺序,然后通知Builder开始建造。
- Builder:抽象Builder类,规范产品的组建,一般由子类实现。
- ConcreteBulider:具体建造者,实现抽象 Builder 类定义的所有方法,并且返回一个组建好的对象。
- Product:产品类。
简单实现
用DIY组装计算机的例子来实现一下建造者模式。
创建产品类
组装一台计算机,计算机被抽象为Computer类,它有3个部件:CPU、主板和内存,并在里面提供了3个方法分别用来设置CPU、主板和内存:
创建Builder类规范产品的组建
商家组装计算机有一套组装方法的模板,就是一个抽象的 Builder 类,其里面提供了安装CPU、主板和内存的方法,以及组装成计算机的create方法
商家实现了抽象的Builder类,MoonComputerBuilder类用于组装计算机
用导演类来统一组装过程
商家的导演类用来规范组装计算机的流程规范,先安装主板,再安装CPU,最后安装内存并组装成计算机:
客户端调用导演类
最后商家用导演类组装计算机。我们只需要提供自己想要的CPU、主板和内存就可以了,至于商家怎样组装计算机我们无须知道
使用场景及优缺点
使用场景
- 当创建复杂对象的算法应该独立于该对象的组成部分以及它们的装配方式时。
- 相同的方法,不同的执行顺序,产生不同的事件结果时。
- 多个部件或零件都可以被装配到一个对象中,但是产生的运行结果又不相同时。
- 产品类非常复杂,或者产品类中的调用顺序不同而产生了不同的效能。
- 在创建一些复杂的对象时,这些对象的内部组成构件间的建造顺序是稳定的,但是对象的内部组成构件面临着复杂的变化。
优点
- 使用建造者模式可以使客户端不必知道产品内部组成的细节。
- 具体的建造者类之间是相互独立的,容易扩展。
- 由于具体的建造者是独立的,因此可以对建造过程逐步细化,而不对其他的模块产生任何影响。
缺点
- 产生多余的Build对象以及导演类。