三种工厂模式关于开闭原则的实现
先了解什么是OCP,一个类可以允许拓展它的功能,而不允许修改它的源码
别着急觉得读懂了,暂且保证什么叫扩展,也就是无论继承还是关联关系的在这个系统增加类去增加方法,而不是试图去改原有的类与代码
如果你很熟悉工厂模式,那么我想分享这句话,工厂模式的定义:
在基类中定义创建对象的一个接口,让子类决定实例化哪个类。工厂方法让一个类的实例化延迟到子类中进行。
重点在于,我们可以将对象的创建和使用分离,避免实例化一个类的数据和代码在多个类中到处都是。
开闭原则的题目
主要是这样考察:你了解三种工厂模式吗?聊聊他们之间的相同与不同处,说说他们如何实现开闭原则。
- 相同在于
- 三种工厂模式都隔离了对象的创建和使用,客户端只需要关系使用产品,而生产产品的过程交给了工厂类
- 都封装了抽象产品类
- 不同在于
- 简单工厂把所有的产品类型都放在一个工厂中,这导致如果需要生产一个新的产品,就不得不回到工厂类的代码中,增添方法,这违背了开闭原则。
- 工厂模式和抽象工厂模式都提供了抽象工厂角色,让具体工厂继承与抽象工厂以实现对产品的创建。但是工厂模式和简单工厂模式是对应一个产品等级结构(单类产品)的,而抽象工厂则是对应多个产品等级结构(形成产品族)的。(记得举例说明的汽水和玩偶吗)
- 专注于开闭原则
- 只有工厂模式真正意义的实现开闭原则
- 简单工厂因为把所有的产品生产都放入一个工厂类,导致新产品不得不修改唯一工厂类的源代码
- 抽象工厂模式则是因为在产品族的定义上,导致抽象工厂模式的扩展有一定的“开闭原则”倾斜性。
- 抽象工厂认为一个品牌是产品的父亲,也就是把所有该品牌下的产品都归于一个工厂类,那么当我们的需求是新增一个新品牌时,只需要新建一个新品牌的工厂,并完整代码即可,这符合OCP。
- 而当该品牌需要一个新的产品的时候,又发生了和简单工厂一样的情况,不得不回到该品牌工厂内新增或修改源码,这不符合OCP。
特殊回答的总结
- 系统中只存在一个等级结构的产品时,抽象工厂模式将退化到工厂方法模式。
- 抽象工厂模式就是对简单工厂模式的进一步抽象(这也导致了他们因为同样的问题发生违法OCP的情况)
单例模式的实现
先知道什么是单例,就是这个类自身创建了自己这个对象并且持续维护唯一一个and提供方法供其他类使用。注意这里已经描写了它的三个特点:创建自身(new),全局唯一,提供方法(getInstance)
实现要点
- 构造器(构造函数)私有
- 含有一个该类的静态变量来保存这个唯一的实例
- 对外提供获取该实例对象的方法
饿汉式
所谓饿汉就是无论要不要,他都很急,所以不论是否需要这个对象都直接new创建并维护
public class M01 {
//这一行
private static final M01 INSTANCE = new M01();
private M01(){}
public static M01 getInstance(){
return INSTANCE;
}
}
优点:
避免了线程同步问题,不存在线程安全问题
缺点:
在类加载的时候就完成了实例化,如果从未使用过该类,造成内存浪费
懒汉式
一个吃饱的人就比较懒,也就是懒加载
当需要的时候再创建对象
public class M03 {
private static M03 INSTANCE;
private M03(){}
public static M03 getInstance(){
//判断当前不存在一个实例再new
if(INSTANCE == null){
INSTANCE = new M03();
}
return INSTANCE;
}
}
在多线程的情况下,线程不安全
public static void main(String[] args) {
for(int i = 0;i<100;i++){
new Thread(()->{
System.out.println(M03.getInstance().hashCode());
}).start();
}
}
一百个线程一起跑,可能出现a和b两个线程同时读取到if(INSTANCE == null)
这句话,都认为当前空间下,不存在实例,都新建了一个实例对象。这时候就不满足唯一实例对象的要求了。
优点:
节省空间
缺点:
线程不安全,题目肯定会要求你说出为什么不安全,请按照上面多线程的分析回答该问题
DCL双重检查
public class M06 {
private static M06 INSTANCE;
private M06(){}
public static M06 volatile getInstance() {
if (INSTANCE == null) {
//当发现空间内不存在实例对象,先锁住当前类
synchronized (M06.class) {
//避免两个进程同时获取到锁
//则当前线程再判断一次是否不存在对象
if(INSTANCE==null){
INSTANCE = new M06();
}
}
}
return INSTANCE;
}
}
基本解决线程不安全和内存空间浪费的问题