03-创建型设计模式

nobility 发布于 2020-05-12 933 次阅读


创建型设计模式

简单工厂模式

定义

由一个工厂对象决定创建出哪一种类实例

UML类图

简单工厂

适用场景

  • 工厂类负责创建的对象比较少
  • 客户只知道传入工厂类的参数,不关注产品类实例如何创建实现等细节

优缺点

优点:只需传入一个正确的参数,就可以获取你所需要的对象,而无需知道创建细节

缺点:工厂类的职责相对过重,增加新的产品需要修改工厂类的判断逻辑,违背开闭原则

工厂方法模式

定义

定义一个创建对象的接口,让其子类来决定实例化那个类,工厂方法让类实例化推迟到子类中进行

UML类图

工厂方法

适用场景

  • 创建对象需要大量重复的代码,客户不依赖产品类实例如何创建实现等细节

  • 一个类通过子类来指定创建那个对象

优缺点

优点:只需关注所需产品对应的工厂方法,无需关注创建细节,复合开闭原则提,高了可扩展性

缺点:类的个数容易过多,增加复杂度

抽象工厂模式

定义

提供一个创建一系列相关或相互依赖对象的接口,无需指定他们具体的类,即将一组拥有同一主题单独工厂封装起来

UML类图

抽象工厂

适用场景

客户不依赖产品类实例如何创建实现等细节,强调不同系列但相关的产品对象一起使用创建对象需要大量重复代码,提供一个产品库,所有该系列相关产品以同样的接口出现,从而使客户不依赖具体实现

优缺点

优点:具体产品在应用层代码隔离,无需关心创建细节,将不同系列但相关产品统一起创建

缺点:规定了所有可能被常见的产品集合,系列产品扩展困难,需要修改抽象工厂接口

单例模式

定义

保证一个类只有一个实例,并且提供一个全局访问点

懒汉式单例模式

线程不安全
public class LazySingleton {
  private static LazySingleton lazySingleton = null;

  private LazySingleton() { 

  }	//构造方法私有化

  public static LazySingleton getInstance() {
    if (lazySingleton == null) {  //若该单例还未实例化
      /* 多线程同时通过了if条件的判断后就会创建多个实例 */
      lazySingleton = new LazySingleton();  //就实例化
    }
    return lazySingleton; //返回单例
  }
}
线程安全
同步方法
public class LazySingleton {
  private static LazySingleton lazySingleton = null;

  private LazySingleton() {

  } //构造方法私有化

  public synchronized static LazySingleton getInstance() {
    /* 静态方法锁的是当前类,虽然能解决线程不安全但是锁的范围过大,而且方法锁性能较差 */
    if (lazySingleton == null) {  //若该单例还未实例化
      lazySingleton = new LazySingleton();  //就实例化
    }
    return lazySingleton; //返回单例
  }
}
双重校验锁
public class LazyDoubleCheckSingleton {
  private volatile static LazyDoubleCheckSingleton lazyDoubleCheckSingleton = null;
  /**
   * 使用volatile关键字是因为创建单例对象时该操作步骤并不是一个原子操作
   *     第一步:为单例对象指定内存空间
   *     第二步:初始化该对象
   *     第三步:让该静态变量指向该内存空间地址
   * 所以若不加volatile关键字声明,则可能导致这几步重排序,若先三后二,就会导致多创建对象
   * 加上该关键字就不会重排序,而且多个线程操作该变量时都在主内存中,所有线程都是可见的,从而保证线程安全
   */
  
  private LazyDoubleCheckSingleton() {

  } //构造方法私有化

  public static LazyDoubleCheckSingleton getInstance() {
    if (lazyDoubleCheckSingleton == null) {  //若该单例还未实例化,多个线程可能同时进入该判断语句
      /* 若没有锁外判断,就与加在方法上的锁性能一致,因为该同步代码块不一定会执行,会减少锁的开销 */
      synchronized (LazyDoubleCheckSingleton.class) {  //锁机制会让线程一个一个的执行同步代码块中的代码
        if (lazyDoubleCheckSingleton == null) { //所以同步锁中要再经历一次判断,除第一个线程以外都会判断失败
          lazyDoubleCheckSingleton = new LazyDoubleCheckSingleton();  //就实例化
        }
      }
    }
    return lazyDoubleCheckSingleton; //返回单例
  }
}
静态内部类
public class StaticInnerClassSingleton {
  private static class InnerClass{  //将单例放在内部类的静态成员变量中,当该内部类加载时才会创建单例对象,并且类只会加载一次
    private static StaticInnerClassSingleton staticInnerClassSingleton = new StaticInnerClassSingleton();
  }
  private StaticInnerClassSingleton() {

  } //构造方法私有化

  public static StaticInnerClassSingleton getInstance() {
    return InnerClass.staticInnerClassSingleton;  //此时静态内部类才会加载,所以实现了延时加载单例的效果
  }
}

饿汉式单例模式

public class HungrySingleton {
  private final static HungrySingleton hungrySingleton = new HungrySingleton();
  //或静态代码块的形式
  //  private final static HungrySingleton hungrySingleton;
  //
  //  static {
  //    hungrySingleton = new HungrySingleton();
  //  }

  private HungrySingleton() {

  } //构造方法私有化

  public static HungrySingleton getInstance() {
    return hungrySingleton; //直接返回类初始化就声明好的单例
  }
}

单例破坏和预防

序列化与反序列化

若需要将单例对象序列化后再反序列化时,反序列化会使用反射重新创建一个实例对象,若还想拿到原来的对象则需要为该单例类中增加一个- readResolve():Object方法,在该方法中返回原来的单例,但是这并不意味着在反序列化中不会重新创建单例对象,只是创建之后没有返回新创建的单例,而是返回的该方法中返回的单例对象

反射攻击

对于私有的构造方法来说,可虽然外部无法通过正常方式访问,但是可以通过反射将构造器权限打开,然后在获取单例对象,需要在构造方法中添加防止反射攻击的代码

if (singleton != null) {	//若单例已经创建
  throw new RuntimeException("禁止反射创建单例对象"):
}

这种方式对于类加载时就将单例创建好的方式是管用的,即饿汉式和懒汉式中的静态内部类中方式管用;若是其他方式时是在获取单例方法中才实例化的对象,若先通过反射调用构造方法创建对象,仍然会出现多个单例对象,这些方式是无法防御的

枚举单例模式

枚举类无法通过发射创建实例对象,序列化与反序列化时也不会创建新的枚举对象,同样也是线程安全的

public enum EnumSingleton {
  enumSingleton;

  public static EnumSingleton getInstance() { //该方法不是必须的,因为可以通过静态属性的方式拿到单例
    return enumSingleton;
  }
}

容器单例模式

import java.util.HashMap;
import java.util.Map;

public class ContainerSingleton {

  private static Map<String, Object> singletonMap = new HashMap<>(); //存放单例的容器
  //使用HashMap容器是线程不安全的,使用HashTable是线程安全的但是性能不高

  private ContainerSingleton() {
  } //构造方法私有化

  /**
   * @MethodName: putInstance
   * @Description: 向单例容器中放置单例对象
   * @Param key: 单例对象的键
   * @Param value: 单例对象
   * @Return void
   */
  public static void putInstance(String key, Object value) {
    if (key != null && !key.isEmpty() && value != null) { //保证键值的合法性
      if (!singletonMap.containsKey(key)) {  //若当前容器中不存在该键才会放置,防止会覆盖掉
        singletonMap.put(key, value);
      }
    }
  }

  /**
   * @MethodName: getInstance
   * @Description: 从容器中获取指定的单例对象
   * @Param key: 指定的键
   * @Return java.lang.Object
   */
  public static Object getInstance(String key) {
    return singletonMap.get(key);
  }
}

线程单例

不保证单例全局唯一,但是保证单例线程唯一

public class ThreadLocalSingleton {
  private static final ThreadLocal<ThreadLocalSingleton> threadLocalSingleton //ThreadLocal容器
      = new ThreadLocal<ThreadLocalSingleton>() {
    @Override
    protected ThreadLocalSingleton initialValue() { //重写initialValue方法
      return new ThreadLocalSingleton();  //默认初始值是该单例对象
    }
  };

  private ThreadLocalSingleton() {

  } //构造方法私有化

  public static ThreadLocalSingleton getInstance() {
    return threadLocalSingleton.get();  //从ThreadLocal容器中获取单例对象
  }
}

适用场景

想确保任何情况下都据对有一个实例

优缺点

优点:在内存中只有一个实例,减少内存的开销,避免对资源的多重占用,设置全局的访问点,严格控制访问

缺点:没有接口,扩展困难

建造者模式

定义

将一个复杂对象的构建与他的表示分离,使得同样的构建过程可以创建不同表示,用户只需指定需要建造的类型就可以得到他们,不需要知道建造过程和细节

UML类图

构建者模式

适用场景

一个对象有非常复杂的内部结构,想将复杂对象的创建和使用分离

优缺点

优点:封装性好,创建和使用分离,扩展性好,建造类之间独立,一定程度上解耦

缺点:会产生多余的建造者对象,产品内部发生变化,建造者也要修改,成本较大

原型模式

定义

指原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象,不需要知道任何创建细节,不调用构造函数

UML类图

原型模式

适用场景

类初始化消耗较多资源和繁琐的数据准备,构造函数比较复杂,以及循环中创建大量对象时

优缺点

优点:比直接new一个对象的性能要高,简化创建过程,产品内部发生变化时克隆出的对象也会随之发生变化

缺点:必须配备克隆方法,对克隆复杂对象或对克隆对象进行复杂改造时容易引入风险,所以深拷贝和浅拷贝要运用得当

此作者没有提供个人介绍
最后更新于 2020-05-12