架构师内功心法:设计模式(二)创建型模式(二)

写在前面:

  • 你好,欢迎关注!
  • 我热爱技术,热爱分享,热爱生活, 我始终相信:技术是开源的,知识是共享的!
  • 博客里面的内容大部分均为原创,是自己日常的学习记录和总结,便于自己在后面的时间里回顾,当然也是希望可以分享 自己的知识。如果你觉得还可以的话不妨关注一下,我们共同进步!
  • 个人除了分享博客之外,也喜欢看书,写一点日常杂文和心情分享,如果你感兴趣,也可以关注关注!
  • 公众号:傲骄鹿先生

目录

四、 建造者模式(Build Pattern)

五、 单例模式(Singleton Pattern)

六、 原型模式( Prototype Pattern)


四、 建造者模式(Build Pattern

建造者模式(builder)是创建一个复杂对象的创建型模式,将构建复杂对象的过程和它的部件解耦,使得构建过程和部件的表示分离开来。
 
1、建造者模式结构图
  • Director: 指挥者类,用于统一组装流程
  • Builder:抽象Builder类,规范产品的组建,一般是由子类实现。
  • ConcreteBulider: 抽象Builder类的实现类,实现抽象Builder类定义的所有方法,并且返回一个组建好的对象
  • Product: 产品类
2、建造者模式的简单实现
这里我们就用DIY组装电脑的例子来实现一下建造者模式。
(1)创建产品类
  我要组装一台电脑,电脑被抽象为Computer类,它有三个部件:CPU 、主板和内存。并在里面提供了三个方法分别用来设置CPU 、主板和内存:
public class Computer {   
    private String mCpu;    
    private String mMainboard;    
    private String mRam;

    public void setmCpu(String mCpu) { 
       this.mCpu = mCpu;
    }

    public void setmMainboard(String mMainboard) {
        this.mMainboard = mMainboard;
    }

    public void setmRam(String mRam) {
        this.mRam = mRam;
    }
}
2)抽象Builder类
商家组装电脑有一套组装方法的模版,就是一个抽象的Builder类,里面提供了安装CPU、主板和内存的方法,以及组装成电脑的create方法:
public abstract class Builder {
    public abstract void buildCpu(String cpu);
    public abstract void buildMainboard(String mainboard);
    public abstract void buildRam(String ram);
    public abstract Computer create();
}

(3)具体Builder类

商家实现了抽象的Builder类,MoonComputerBuilder类用于组装电脑:

public class MoonComputerBuilder extends Builder {
    private Computer mComputer = new Computer();
    @Override    public void buildCpu(String cpu) { 
       mComputer.setmCpu(cpu);
    }

    @Override
    public void buildMainboard(String mainboard) {
        mComputer.setmMainboard(mainboard);
    }

    @Override
    public void buildRam(String ram) {
        mComputer.setmRam(ram);
    }

    @Override
    public Computer create() {
        return mComputer;
    }
}

(4)具体指挥者(Director)类

商家的指挥者类用来规范组装电脑的流程规范,先安装主板,再安装CPU,最后安装内存并组装成电脑:
public class Direcror {
    Builder mBuild=null;
    public Direcror(Builder build){
       this.mBuild=build;
    }
    public Computer CreateComputer(String cpu,String mainboard,String ram){
       //规范建造流程
       this.mBuild.buildMainboard(mainboard);
       this.mBuild.buildCpu(cpu);
       this.mBuild.buildRam(ram);
       return mBuild.create();
    }
}

(5)客户端调用指挥者(Director)类

最后商家用指挥者类组装电脑。我们只需要提供我们想要的CPU,主板和内存就可以了,至于商家怎样组装电脑我们无需知道。
public class CreatComputer {
    public static void main(String[]args){
        Builder mBuilder=new MoonComputerBuilder();
        Direcror mDirecror=new Direcror(mBuilder);
        //组装电脑
        mDirecror.CreateComputer("i7-6700","华擎玩家至尊","三星DDR4");
    }
}

3、优点和缺点

(1)优点

  • 使用建造者模式可以使客户端不必知道产品内部组成的细节。

  • 具体的建造者类之间是相互独立的,容易扩展。

  • 由于具体的建造者是独立的,因此可以对建造过程逐步细化,而不对其他的模块产生任何影响。

(2)缺点

  • 产生多余的Build对象以及Dirextor类。

4、适用场景

  • 当创建复杂对象的算法应该独立于该对象的组成部分以及它们的装配方式时。

  • 相同的方法,不同的执行顺序,产生不同的事件结果时。

  • 多个部件或零件,都可以装配到一个对象中,但是产生的运行结果又不相同时。

  • 产品类非常复杂,或者产品类中的调用顺序不同产生了不同的效能。

  • 创建一些复杂的对象时,这些对象的内部组成构件间的建造顺序是稳定的,但是对象的内部组成构件面临着复杂的变化。

五、 单例模式(Singleton Pattern

单例模式是确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例。实现单例模式有几个关键点:
  1. 构造函数不对外开放---Private
  2. 通过一个静态方法或者枚举返回单利对象
  3. 确保单例类的对象有且只有一个,尤其是在多线程环境下。
  4. 确保单例类对象在反序列化时不会重新构建对象。
1、单例模式的简单实现
单例模式的实现方式有多种,根据需求场景,可分为2大类、6种实现方式。具体如下:
 
初始化单例类时创建单例
(1)饿汉式
原理: 依赖 JVM类加载机制,保证单例只会被创建1次,即 线程安全
  1. JVM在类的初始化阶段(即 在Class被加载后、被线程使用前),会执行类的初始化
  2. 在执行类的初始化期间,JVM会去获取一个锁。这个锁可以同步多个线程对同一个类的初始化
应用场景: 除了初始化单例类时 即 创建单例外,继续延伸出来的是:单例对象 要求初始化速度快 & 占用内存小
public class HungrySingleton {
    private static  final HungrySingleton mSingleton= new HungrySingleton();
    //私有构造函数
    private HungrySingleton(){
    }
    //公有的静态函数,对外暴露获取单例对象的接口
    public static HungrySingleton getSingleton(){
        return mSingleton;
    }
}

(2)枚举类型

原理: 满足单例模式所需的 创建单例、线程安全、实现简洁的需求
单元素的枚举类型已经成为实现 Singleton的最佳方法
public enum Singleton{

    //定义1个枚举的元素,即为单例类的1个实例
    INSTANCE;

    // 隐藏了1个空的、私有的 构造方法
    // private Singleton () {}
}
// 获取单例的方式:
Singleton singleton = Singleton.INSTANCE;
按需、延迟创建单例
(1)懒汉式(基础实现)
class Singleton {
    // 1. 类加载时,先不自动创建单例
   //  即,将单例的引用先赋值为 Null
    private static  Singleton ourInstance  = null;

    // 2. 构造函数 设置为 私有权限
    // 原因:禁止他人创建实例
    private Singleton() {
    }
    // 3. 需要时才手动调用 newInstance() 创建 单例   
    public static  Singleton newInstance() {
    // 先判断单例是否为空,以避免重复创建
    if( ourInstance == null){
        ourInstance = new Singleton();
        }
        return ourInstance;
    }
}

基础实现的懒汉式是线程不安全的,具体原因如下:

(2)懒汉式——同步锁优化
原理: 使用同步锁 synchronized锁住 创建单例的方法 ,防止多个线程同时调用,从而避免造成单例被多次创建
  • 即,getInstance()方法块只能运行在1个线程中
  • 若该段代码已在1个线程中运行,另外1个线程试图运行该块代码,则 会被阻塞而一直等待
  • 而在这个线程安全的方法里我们实现了单例的创建,保证了多线程模式下 单例对象的唯一性
缺点: 每次访问都要进行线程同步(即 调用synchronized锁),造成过多的同步开销(加锁 = 耗时、耗能)
// 写法1
class Singleton {
    // 1. 类加载时,先不自动创建单例
    //  即,将单例的引用先赋值为 Null
    private static  Singleton ourInstance  = null;
    
    // 2. 构造函数 设置为 私有权限
    // 原因:禁止他人创建实例
    private Singleton() {
    }
    
    // 3. 加入同步锁
    public static synchronized Singleton getInstance(){
        // 先判断单例是否为空,以避免重复创建
        if ( ourInstance == null )
            ourInstance = new Singleton();
        return ourInstance;
    }
}

// 写法2// 该写法的作用与上述写法作用相同,只是写法有所区别
class Singleton{
    private static Singleton instance = null;
    private Singleton(){
    }
    public static Singleton getInstance(){
        // 加入同步锁
        synchronized(Singleton.class) {
            if (instance == null)
                instance = new Singleton();
        }
        return instance;
    }
}

(3)懒汉式——双重锁优化

原理: 在同步锁的基础上,添加1层 if判断:若单例已创建,则不需再执行加锁操作就可获取实例,从而提高性能
class Singleton {
    private static  Singleton ourInstance  = null;

    private Singleton() {}

    public static  Singleton newInstance() {
        // 加入双重校验锁
        // 校验锁1:第1个if
        if( ourInstance == null){  // ①
          synchronized (Singleton.class){ // ②
              // 校验锁2:第2个 if
              if( ourInstance == null){
                  ourInstance = new Singleton();
          }
        }
     }
     return ourInstance;
   }
}

检验锁1: 第一个if

  • 作用:若单例已创建,则直接返回已创建的单例,无需再执行加锁操作。即直接跳到执行 return ourInstance
检验锁2:第二个if
  • 作用:防止多次创建单例问题
  • 原理:
    • 线程A调用newInstance(),当运行到②位置时,此时线程B也调用了newInstance()
    • 因线程A并没有执行instance = new Singleton();,此时instance仍为空,因此线程B能突破第1层if 判断,运行到①位置等待synchronized中的A线程执行完毕
    • 当线程A释放同步锁时,单例已创建,即instance已非空
    • 此时线程B 从①开始执行到位置②。此时第2层if判断 = 为空(单例已创建),因此也不会创建多余的实例
 
(4)静态内部类
public class StaticInnerSingleton {
    private StaticInnerSingleton(){}
    public static StaticInnerSingleton getInstance(){
        return InstanceHolder .mStaticInnerSingleton;
    }
    //静态内部类
    private static class InstanceHolder {
        private static StaticInnerSingleton mStaticInnerSingleton=new StaticInnerSingleton();
    }
}

JVM在类的初始化阶段(即在Class被加载后,且被线程使用之前),会执行类的初始化。在执行类的初始化期间,JVM会去获取一个锁。这个锁可以同步多个线程对同一个类的初始化。

由于Java语言是多线程的,多个线程可能在同一时间尝试去初始化同一个类或接口(比如这里多个线程可能在同一时刻调用getInstance()方法来初始化InstanceHolder类)。因此,在Java中初始化一个类或者接口时,需要做细致的同步处理。 Java语言规范规定,对于每一个类或接口C,都有一个唯一的初始化锁LC与之对应。从C到LC的映射,由JVM的具体实现去自由实现。JVM在类初始化期间会获取这个初始化锁,并且每个线程至少获取一次锁来确保这个类已经被初始化过了。
 
当第一次加载Singleton类时并不会初始化sInstance,只有在第一次调用Singleton的getInstance方法时才会导致sInstance被初始化。因此,第一次调用getInstance方法会导致虚拟机加载SingletonHolder类,这种方式不仅能够确保线程安全,也能够保证单例对象的唯一性,同时也延迟了单例的实例化
 
(5)使用容器实现单例模式
public 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获取对象对应类型的对象。

这种方式使得我们可以管理多种类型的单例,并且在使用可以通过统一的接口进行获取操作,降低了用户使用成本,也对用户隐藏了具体实现,降低了耦合度
 
反序列化获得单例
 
在反序列化的情况下他们会出现重新创建对象。构造函数是私有的,反序列化依然可以通过特殊的途径去创建类的一个新的实例,相当于调用该类的构造函数。反序列化提供了一个特别的钩子函数,类中具有一个私有的readResolve()函数,这个函数可以让程序员控制对象的反序列化。如果要杜绝单例对象在被反序列化时重新生成对象,那么必须加入readResolve函数。
public class SerializableSingleton implements Serializable {
    private static final long serialVersionUID=0L;
    private static final SerializableSingleton INSTANCE =new SerializableSingleton();
    private SerializableSingleton(){}
    public static SerializableSingleton getSerializableSingleton(){
        return INSTANCE;
    }
    private Object readResolve() throws ObjectStreamException{
        return INSTANCE;
    }
}

readResolve方法中将单例对象返回,而不是重新生成一个新的对象。而对于枚举则不存在这个问题。

六、原型模式( Prototype Pattern

原型模式属于对象的创建模式。通过给出一个原型对象来指明所有创建的对象的类型,然后用复制这个原型对象的办法创建出更多同类型的对象。这就是选型模式的用意。
原型模式要求对象实现一个可以“克隆”自身的接口,这样就可以通过复制一个实例对象本身来创建一个新的实例。这样一来,通过原型实例创建新的对象,就不再需要关心这个实例本身的类型,只要实现了克隆自身的方法,就可以通过这个方法来获取新的对象,而无须再去通过new来创建。
原型模式有两种表现形式:(1)简单形式(2)登记形式  ;这两种表现形式仅仅是原型模式的不同实现。
 
1、简单的原型模式
(1)简单的原型模式结构图
这种形式涉及到三个角色:
  (1)客户(Client)角色:客户类提出创建对象的请求。
  (2)抽象原型(Prototype)角色:这是一个抽象角色,通常由一个Java接口或Java抽象类实现。此角色给出所有的具体原型类所需的接口。
  (3)具体原型(Concrete Prototype)角色:被复制的对象。此角色需要实现抽象的原型角色所要求的接口。
(2)简单实现
抽象原型:
public interface Prototype{
    /**
     * 克隆自身的方法
     * @return 一个从自身克隆出来的对象
     */
    public Object clone();
}
具体原型:
public class ConcretePrototype1 implements Prototype {
    public Prototype clone(){
        //最简单的克隆,新建一个自身对象,由于没有属性就不再复制值了
        Prototype prototype = new ConcretePrototype1();
        return prototype;
    }
}

public class ConcretePrototype2 implements Prototype {
    public Prototype clone(){
        //最简单的克隆,新建一个自身对象,由于没有属性就不再复制值了
        Prototype prototype = new ConcretePrototype2();
        return prototype;
    }
}

客户端角色:

public class Client {

    //持有需要使用的原型接口对象
    private Prototype prototype;
    //构造方法,传入需要使用的原型接口对象
    public Client(Prototype prototype){
        this.prototype = prototype;
    }
    public void operation(Prototype example){
        //需要创建原型接口的对象
        Prototype copyPrototype = prototype.clone();
    }
}

2、登记形式的原型模式

(1)登记形式的原型模式结构图

作为原型模式的第二种形式,它多了一个原型管理器(PrototypeManager)角色,该角色的作用是:创建具体原型类的对象,并记录每一个被创建的对象。

(2)简单实现

抽象原型角色:

public interface Prototype{
    public Prototype clone();
    public String getName();
    public void setName(String name);
}

具体原型角色:

public class ConcretePrototype1 implements Prototype {
    private String name;
    public Prototype clone(){
        ConcretePrototype1 prototype = new ConcretePrototype1();
        prototype.setName(this.name);
        return prototype;
    }
    public String toString(){
        return "Now in Prototype1 , name = " + this.name;
    }
    @Override
    public String getName() {
        return name;
    }
    @Override
    public void setName(String name) {
        this.name = name;
    }
}

public class ConcretePrototype2 implements Prototype {
    private String name;
    public Prototype clone(){
        ConcretePrototype2 prototype = new ConcretePrototype2();
        prototype.setName(this.name);
        return prototype;
    }
    public String toString(){
        return "Now in Prototype2 , name = " + this.name;
    }
    @Override
    public String getName() {
        return name;
    }

    @Override
    public void setName(String name) {
        this.name = name;
    }
}

原型管理器角色保持一个聚集,作为对所有原型对象的登记,这个角色提供必要的方法,供外界增加新的原型对象和取得已经登记过的原型对象。

public class PrototypeManager {
    /**
     * 用来记录原型的编号和原型实例的对应关系
     */
    private static Map<String,Prototype> map = new HashMap<String,Prototype>();
    /**
     * 私有化构造方法,避免外部创建实例
     */
    private PrototypeManager(){}
    /**
     * 向原型管理器里面添加或是修改某个原型注册
     * @param prototypeId 原型编号
     * @param prototype    原型实例
     */
    public synchronized static void setPrototype(String prototypeId , Prototype prototype){
        map.put(prototypeId, prototype);
    }
    /**
     * 从原型管理器里面删除某个原型注册
     * @param prototypeId 原型编号
     */
    public synchronized static void removePrototype(String prototypeId){
        map.remove(prototypeId);
    }
    /**
     * 获取某个原型编号对应的原型实例
     * @param prototypeId    原型编号
     * @return    原型编号对应的原型实例
     * @throws Exception    如果原型编号对应的实例不存在,则抛出异常
     */
    public synchronized static Prototype getPrototype(String prototypeId) throws Exception{
        Prototype prototype = map.get(prototypeId);
        if(prototype == null){
            throw new Exception("您希望获取的原型还没有注册或已被销毁");
        }
        return prototype;
    }
}

客户端角色:

public class Client {
    public static void main(String[]args){
        try{
            Prototype p1 = new ConcretePrototype1();
            PrototypeManager.setPrototype("p1", p1);
            //获取原型来创建对象
            Prototype p3 = PrototypeManager.getPrototype("p1").clone();
            p3.setName("张三");
            System.out.println("第一个实例:" + p3);
            //有人动态的切换了实现
            Prototype p2 = new ConcretePrototype2();
            PrototypeManager.setPrototype("p1", p2);
            //重新获取原型来创建对象
            Prototype p4 = PrototypeManager.getPrototype("p1").clone();
            p4.setName("李四");
            System.out.println("第二个实例:" + p4);
            //有人注销了这个原型
            PrototypeManager.removePrototype("p1");
            //再次获取原型来创建对象
            Prototype p5 = PrototypeManager.getPrototype("p1").clone();
            p5.setName("王五");
            System.out.println("第三个实例:" + p5);
        }catch(Exception e){
            e.printStackTrace();
        }
    }
}

3、优点和缺点

(1)优点
原型模式允许在运行时动态改变具体的实现类型。原型模式可以在运行期间,由客户来注册符合原型接口的实现类型,也可以动态地改变具体的实现类型,看起来接口没有任何变化,但其实运行的已经是另外一个类实例了。因为克隆一个原型就类似于实例化一个类。
(2)缺点
原型模式最主要的缺点是每一个类都必须配备一个克隆方法。配备克隆方法需要对类的功能进行通盘考虑,这对于全新的类来说不是很难,而对于已经有的类不一定很容易,特别是当一个类引用不支持序列化的间接对象,或者引用含有循环结构的时候。
 
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章