【设计模式二】创建型模式(单例/工厂/原型/建造者)

四、创建型模式

1、单例模式

Singleton Pattern

1)模式介绍

所谓类的单例设计模式,就是采取一定的方法保证在整个的软件系统中,对某个类只能存在一个对象实例,并且该类只提供一个取得其对象实例的方法(静态方法)

比如 Hibernate的 SessionFactory,它充当数据存储源的代理,并负责创建 Session对象。SessionFactory并不是轻量级的,一般情况下,一个项目通常只需要一个 SessionFactory就够,这时就会使用到单例模式

2)八种方式

1、饿汉式(静态常量)

2、饿汉式(静态代码块)

优点:线程安全。在类装载的时候就完成实例化。避免了线程同步问题

缺点:没有达到 Lazy Loading 的效果。如果从始至终从未使用过这个实例,则会造成内存的浪费

饿汉式单例模式可能造成内存浪费

3、懒汉式(线程不安全)

class Singleton {
	// 静态变量
    private static Singleton instance;
	/**
	* 私有构造器
	*/
    private Singleton() {}
    /**
    * 懒汉式 
    * 提供一个静态的公有方法,当使用到该方法时,才去创建  instance
    */
	public static Singleton getInstance() {
		if(instance == null) {
			instance = new Singleton();
		}
		return instance;
	}
}
  • 起到了 Lazy Loading 的效果,但是只能在单线程下使用。

  • 如果在多线程下,一个线程进入了 if (singleton == null) 判断语句块,还未来得及往下执行,另一个线程也通过了这个判断语句,这时便会产生多个实例。所以在多线程环境下不可使用这种方式

  • 在实际开发中,不要使用这种方式

4、懒汉式(线程安全,同步方法)

/**
* 懒汉式 
* 提供一个静态的公有方法,当使用到该方法时,才去创建  instance
*/
public static synchronized Singleton getInstance() {
    if(instance == null) {
        instance = new Singleton();
    }
    return instance;
}
  • 解决了线程安全问题
  • 效率太低了,每个线程在想获得类的实例时候,执行 getInstance() 方法都要进行同步。而其实这个方法只执行一次实例化代码就够了,后面的想获得该类实例,直接 return 就行了。方法进行同步效率太低
  • 在实际开发中,不推荐使用这种方式

5、懒汉式(线程不安全,同步代码块)

/**
* 懒汉式 
* 提供一个静态的代码块
*/
public static Singleton getInstance() {
    if(instance == null) {
        synchronized (Singleton.class){
           instance = new Singleton(); 
        }        
    }
    return instance;
}

if(instance == null) 线程不安全。不推荐

6、DCL 双重检查锁(推荐)

/**
* @author ajun
* Date 2021/7/8
* @version 1.0
* 单例模式:懒汉式(双重检测锁)
*/
public class DCLLazyDemo {
  //先定义类变量,不创建
  //加上volatile,禁止指令重排
  private volatile static DCLLazyDemo dclLazyDemo = null;
    
  /**
  * 私有构造器
  */
  private DCLLazyDemo(){}
    
  /**
  * 获取类对象的对外接口
  */    
  public static DCLLazyDemo getInstance(){
    // 使用的时候,再用new创建
    // 双重检测锁 DCL
    if(dclLazyDemo == null){
      synchronized (DCLLazyDemo.class){
        if(dclLazyDemo == null){
          dclLazyDemo = new DCLLazyDemo();
          /**
          * 由于对象创建不是原子性操作
          * 1. 分配内存空间
          * 2. 使用构造器创建对象
          * 3. 将对象指向内存空间
          */
          /**
          * 可能会发生指令重排
          * 123
          *
          * 132
          *
          * 这是就需使用volatile 关键字来防止指令重排
          */
       }
     }
   }
    return dclLazyDemo;
 }
  // 多线程下不加volatile,new时,虽然有双重检测,但仍有可能会发生指令重排,是不安全的。所以要加上 volatile
  public static void main(String[] args) {
    for (int i = 0; i < 20; i++) {
      new Thread(() -> {
        System.out.println(DCLLazyDemo.getInstance());
     }).start();
   }
 }
}

DCL:Double Check Lock

volatile:保证可见性;不保证原子性;禁止指令重排

线程安全;延迟加载;效率较高;推荐使用

7、静态内部类(推荐)

package singleMode;
/**
* @author ajun
* Date 2021/7/8
* @version 1.0
* 单例模式:内部静态类
*/
public class InnerDemo {
  // 私有构造器
  private InnerDemo(){}
    
  // 静态内部类
  private static class InnerClass{
    private static final InnerDemo INNER_DEMO = new InnerDemo();
 }
    
  // 获取类对象的对外接口
  public static InnerDemo getInstance(){
    return  InnerClass.INNER_DEMO;
 }
}
  • 采用了类装载的机制来保证初始化实例时只有一个线程

  • 静态内部类方式在 外部类 被装载时并 不会立即实例化,而是在需要实例化时,调用 getInstance 方法,才会装载内部类,从而通过内部类完成外部类的实例化

  • 类的静态属性只会在第一次加载类的时候初始化,所以在这里,JVM帮助我们保证了线程的安全性在类进行初始化时,别的线程是无法进入的

  • 优点:线程安全,利用静态内部类特点实现延迟加载效率高代码结构清晰

  • 结论:推荐使用

8、枚举(推荐)

package singleton;

/**
 * @author ajun
 * Date 2021/7/23
 * @version 1.0
 * 单例模式:枚举
 */
public class EnumDemo {
    public static void main(String[] args) {
        Singleton a = Singleton.A;
        Singleton a1 = Singleton.A;
        Singleton b = Singleton.B;
        Singleton c = Singleton.C;
        System.out.println(a.hashCode());
        System.out.println(a1.hashCode());
        System.out.println(b.hashCode());
        System.out.println(c.hashCode());
        System.out.println(b.name());
        System.out.println(b.ordinal());
        System.out.println(c.ordinal());
        a.say();
        b.say();
        c.say();
    }
}
enum Singleton{
    // 枚举值默认为从0开始的有序数值
    A,    B,    C;
    public void say(){
        System.out.println("OK!");
    }
}

  • 借助 JDK1.5 中添加的枚举来实现单例模式。线程安全,防止反序列化(反射)重新创建新的对象
  • 这种方式是 Effective Java作者 Josh Bloch提倡的方式
  • 结论:推荐使用

3)JDK案例

java.lang.Runtime 就是经典的 单例模式(饿汉式)

2、工厂模式

Factory Pattern

  • 传统模式

优点:比较好理解,简单易操作

缺点:违反了设计模式的 ocp 原则,即对扩展开放,对修改关闭。扩展性差。

1)简单工厂模式

  • 简单工厂模式是属于创建型模式,是工厂模式的一种。简单工厂模式是由一个工厂对象决定创建出哪一种产品类的实例。简单工厂模式是工厂模式家族中最简单实用的模式
  • 定义了一个创建对象的类,由这个类来封装实例化对象的行为(代码)
  • 在软件开发中,当大量的创建对象时,就会使用到工厂模式

2)工厂方法模式

定义了一个创建对象的抽象方法,由子类决定要实例化的类。工厂方法模式将对象的实例

化推迟到子类

3)抽象工厂模式

  • 抽象工厂模式:定义了一个 interface 用于创建相关或有依赖关系的对象簇,而无需指明具体的类
  • 抽象工厂模式可以将 简单工厂模式工厂方法模式 进行整合
  • 从设计层面看,抽象工厂模式就是对简单工厂模式的改进(或者称为进一步的抽象)
  • 将工厂抽象成两层,AbsFactory(抽象工厂) 和具体实现的工厂子类。可以根据创建对象类型使用对应的工厂子类。这样将单个的简单工厂类变成了工厂簇,更利于代码的维护和扩展

4)工厂模式小结

  • 工厂模式的意义

将实例化对象的代码提取出来,放到一个类中统一管理和维护,达到和主项目的依赖关系的解耦。从而提高项目的扩展和维护性

  • 三种工厂模式

简单工厂模式、工厂方法模式、抽象工厂模式

工厂方法模式 是 简单工厂模式 的一种升级

  • 设计模式的依赖倒置原则

面向接口编程

  • 创建对象实例时,不要直接 new 类,而是把这个 new 类的动作放在一个工厂的方法中,并返回。有的书上说, 变量不要直接持有具体类的引用
  • 不要让类继承具体类,而是继承抽象类或者是实现 interface(接口)
  • 不要覆盖基类中已经实现的方法

抽象工厂模式案例:组装电脑 | 不同操作系统视窗样式

3、原型模式

Prototype Pattern

1)基本介绍

  • 原型模式(Prototype模式)是指:用原型实例指定创建对象的种类,并且通过拷贝这些原型,创建新的对象
  • 原型模式是一种创建型设计模式,允许一个对象再创建另外一个可定制的对象,无需知道如何创建的细节
  • 工作原理是:通过将一个原型对象传给那个要发起创建的对象,这个要发起创建的对象通过请求原型对象,拷贝它们自己来实施创建,即对象.clone()
  • 被拷贝的类需要实现 Cloneable 接口;如果要序列化,就需要实现 Serializable 接口
  • 形象的理解:孙大圣拔出猴毛,变出其它孙大圣

2)原理类图

UML 类图

  • Prototype :原型类,声明一个克隆自己的接口
  • ConcretePrototype:具体的原型类,实现一个克隆自己的操作
  • Client:让一个原型对象克隆自己,从而创建一个新的对象(属性一样)

3)浅拷贝(默认)

  • 对于数据类型是基本数据类型的成员变量,浅拷贝会直接进行值传递,也就是将该属性值复制一份给新的对象

  • 对于数据类型是引用数据类型的成员变量,比如说成员变量是某个数组、某个类的对象等,那么浅拷贝会进行引用传递,也就是只是将该成员变量的引用值(内存地址)复制一份给新的对象。因为实际上两个对象的该成员变量都指向同一个实例。在这种情况下,在一个对象中修改该成员变量会影响到另一个对象的该成员变量值

  • 浅拷贝是使用默认的 clone() 方法来实现

    sheep = (Sheep) super.clone();

4)深拷贝

  • 复制对象的所有基本数据类型的成员变量值
  • 为所有引用数据类型的成员变量申请存储空间,并复制每个引用数据类型成员变量所引用的对象,直到该对象可达的所有对象。也就是说,对象进行深拷贝要对整个对象(包括对象的引用类型)进行拷贝
  • 深拷贝实现方式1:重写 clone() 方法来实现深拷贝
// 深拷贝-方式1  使用重写 clone() 方法
@Override
protected Object clone() throws CloneNotSupportedException {
	Object deep = null;
	//这里完成对基本数据类型(属性)和String的克隆
	deep = super.clone();
	//对引用类型的属性,进行单独处理
	DeepProtoType deepProtoType = (DeepProtoType)deep;
	deepProtoType.deepCloneableTarget = (DeepCloneableTarget)deepCloneableTarget.clone();

	return deepProtoType;
}
  • 深拷贝实现方式2:通过 对象序列化 实现深拷贝(推荐)

被拷贝对象及对象中的引用类型,都需要实现 Serializable 接口

//深拷贝-方式2 通过对象的序列化实现(推荐)
public Object deepClone() {
    //创建流对象
    ByteArrayOutputStream bos = null;
    ObjectOutputStream oos = null;
    
    ByteArrayInputStream bis = null;
    ObjectInputStream ois = null;
    
    try {
        //序列化
        bos = new ByteArrayOutputStream();
        oos = new ObjectOutputStream(bos);
        oos.writeObject(this); //当前这个对象以对象流的方式输出
        //反序列化
        bis = new ByteArrayInputStream(bos.toByteArray());
        ois = new ObjectInputStream(bis);
        DeepProtoType copyObj = (DeepProtoType)ois.readObject();
        return copyObj;        
    } catch (Exception e) {
        e.printStackTrace();
        return null;
    } finally {
        // 关闭流
        try {
            bos.close();
            oos.close();
            bis.close();
            ois.close();
        } catch (Exception e2) {
            System.out.println(e2.getMessage());
        }
	}
}

5)注意事项和细节

  • 创建新的对象比较复杂时,可以利用原型模式简化对象的创建过程,同时也能够提高效率
  • 不用重新初始化对象,而是动态地获得对象运行时的状态
  • 如果原始对象发生变化(增加或者减少属性),其它克隆的对象也会发生相应的变化,无需修改代码
  • 在实现深克隆的时候可能需要比较复杂的代码
  • 缺点:需要为每一个类配备一个克隆方法,这对全新的类来说不是很难,但对已有的类进行改造时,需要修改其源代码,违背了 ocp 原则

4、建造者模式

Builder Pattern

1)基本介绍

  • 建造者模式(Builder Pattern)又叫生成器模式,是一种对象构建模式
  • 可以将复杂对象的建造过程抽象出来(抽象类别),使这个抽象过程的不同实现方法可以构造出不同表现(属性)的对象
  • 建造者模式是一步一步创建一个复杂的对象,它允许用户只通过指定复杂对象的类型和内容就可以构建它们,

用户不需要知道内部的具体构建细节

2)四个角色

  • 产品角色(Product):具体的产品对象
  • 抽象建造者(Builder):创建 Product 对象的各个部件指定的接口/抽象类
  • 具体建造者(ConcreteBuilder):实现接口,构建和装配各个部件
  • 指挥者/经理(Director):构建使用 Builder 接口的对象。主要是用于创建复杂的对象
    • 隔离了客户与产品(对象)的生产过程;
    • 负责控制产品(对象)的生产过程

3)原理类图

4)案例

  • 需要建房子:过程为打桩、砌墙、封顶
  • 房子有各种各样的,比如普通房,高楼,别墅,各种房子的建造过程虽然一样,但是细节不同

1、房子

产品:Product

package builder;

/**
 * @author ajun
 * Date 2021/7/24
 * @version 1.0
 * 产品:房子
 * Product(产品角色): 一个具体的产品对象
 */
public class House {
    // 地基
    private String baise;
    // 墙
    private String wall;
    // 房顶
    private String roof;

    public String getBaise() {
        return baise;
    }

    public void setBaise(String baise) {
        this.baise = baise;
    }

    public String getWall() {
        return wall;
    }

    public void setWall(String wall) {
        this.wall = wall;
    }

    public String getRoof() {
        return roof;
    }

    public void setRoof(String roof) {
        this.roof = roof;
    }
}

2、施工队

抽象建造者:Builder

package builder;

/**
 * @author ajun
 * Date 2021/7/24
 * @version 1.0
 * 抽象建造者
 * Builder(抽象建造者):创建一个 Product 对象的各个部件指定的 接口 / 抽象类
 */
public abstract class HouseBuilder {
    // 把产品组合进来
    protected  House house = new House();

    /**
     * 抽象方法
     * 房子的建造过程
     */
    abstract void buildBasic();
    abstract void buildWall();
    abstract void buildRoof();

    /**
     * 建造完成
     * @return
     */
    public House completeHouse(){
        return house;
    }
}

3、普通房子施工队

具体建造者:ConcreteBuilder

package builder;

/**
 * @author ajun
 * Date 2021/7/24
 * @version 1.0
 * 具体建造者:普通房子
 * ConcreteBuilder(具体建造者):实现接口,构建和装配各个部件
 */
public class CommonHouseBuilder extends HouseBuilder {
    @Override
    public void buildBasic() {
        System.out.println("普通房子打地基:2米");
    }

    @Override
    public void buildWall() {
        System.out.println("普通房子彻墙:5米");
    }

    @Override
    public void buildRoof() {
        System.out.println("普通房子建房顶");
    }
}

4、高楼施工队

具体建造者:ConcreteBuilder

package builder;

/**
 * @author ajun
 * Date 2021/7/24
 * @version 1.0
 * 具体建造者:高楼
 * ConcreteBuilder(具体建造者):实现接口,构建和装配各个部件
 */
public class HighHouseBuilder extends HouseBuilder{
    @Override
    public void buildBasic() {
        System.out.println("高楼打地基:20米");
    }

    @Override
    public void buildWall() {
        System.out.println("高楼彻墙:50米");
    }

    @Override
    public void buildRoof() {
        System.out.println("高楼建房顶");
    }
}

5、包工头

指挥者/经理:Director

package builder;

/**
 * @author ajun
 * Date 2021/7/24
 * @version 1.0
 * 指挥者(经理)
 * Director(指挥者/经理):构建一个使用  Builder 接口的对象
 * 主要是用于创建一个复杂的产品(对象)
 * 主要作用:一、隔离了客户与产品(对象)的生产过程,二、负责控制产品(对象)的生产过程
 */
public class HouseDirector {
    // 把建造者聚合进来
    HouseBuilder houseBuilder;

    /**
     * 构造器注入
     * @param houseBuilder
     */
    public HouseDirector(HouseBuilder houseBuilder){
        this.houseBuilder = houseBuilder;
    }

    public House constructHouse(){
        if(houseBuilder == null){
            System.out.println("请先指定建造者");
            return null;
        }else{
            houseBuilder.buildBasic();
            houseBuilder.buildWall();
            houseBuilder.buildRoof();
            return houseBuilder.completeHouse();
        }
    }

    /**
     * setter注入
     * @param houseBuilder
     */
    public void setHouseBuilder(HouseBuilder houseBuilder) {
        this.houseBuilder = houseBuilder;
    }
}
  • 客户
package builder;

/**
 * @author ajun
 * Date 2021/7/24
 * @version 1.0
 * 客户:需要产品(房子)
 */
public class Client {
    public static void main(String[] args) {
        // 需要普通房子
        // 普通房子建造者
        CommonHouseBuilder chb = new CommonHouseBuilder();
        // 指挥者
        HouseDirector hd = new HouseDirector(chb);
        // 得到房子
        hd.constructHouse();

        System.out.println("-------------");

        // 需要高楼
        // 高楼建造者
        HighHouseBuilder hhb = new HighHouseBuilder();
        // 把高楼建造者赋给指挥者管理
        hd.setHouseBuilder(hhb);
        // 得到房子
        hd.constructHouse();
    }
}
// 运行结果
普通房子打地基:2米
普通房子彻墙:5米
普通房子建房顶
-------------
高楼打地基:20米
高楼彻墙:50米
高楼建房顶

5)注意事项和细节

  • 客户端(使用程序)不必知道产品内部组成的细节,将 产品本身产品的创建过程 解耦,使得相同的创建过程可以创建不同的产品对象

  • 每一个具体建造者都相对独立,而与其他的具体建造者无关,因此可以很方便地替换具体建造者或增加新的具 体建造者,用户使用不同的具体建造者即可得到不同的产品对象

  • 可以更加精细地控制产品的创建过程。将复杂产品的创建步骤分解在不同的方法中,使得创建过程更加清晰, 也更方便使用程序来控制创建过程

  • 增加具体建造者无须修改原有代码,指挥者类针对抽象建造者类编程,系统扩展方便,符合“开闭原则”

  • 建造者模式所创建的产品一般具有较多的共同点,其组成部分相似,如果产品之间的差异性很大,则不适合使 用建造者模式,因此其使用范围受到一定的限制

  • 如果产品的内部变化复杂,可能会导致需要定义很多具体建造者类来实现这种变化,导致系统变得很庞大,因此在这种情况下,要考虑是否选择建造者模式

  • 抽象工厂模式 VS 建造者模式

    • 抽象工厂模式

      实现对产品家族的创建,一个产品家族是这样的一系列产品:具有不同分类维度的产品组合,采用抽象工厂模式不需要关心构建过程,只关心什么产品由什么工厂生产即可

    • 建造者模式

      重视建造过程。要求按照指定的蓝图建造产品,它的主要目的是通过组装零配件而产生一个新产品

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

土味儿~

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值