设计模式--2创建型模式【五种】

73 阅读13分钟

2 设计模式--创建型模式【五种】

  1. 创建型模式的关注点是 "怎/样创建出对象"
  2. 将对象的创建 与 使用分离
  3. 降低系统耦合度
  4. 使用者无需关注对象的创建细节
  • 对象的创建由相关的工厂完成 (各种工厂模式)
  • 对象的创建由一个建造者来完成(建造者模式)
  • 对象的创建由原来对象克隆完成(原型模式)
  • 对象是在在系统中只有一个实例(单例模式)

2.1 单例模式

Ensure a class has only one instance, and provide a global point of access to it.

确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例。

单例特点:

  1. 某个类只能有一个实例(构造器私有
  2. 它必须自行创建这个实例(自己编写实例化逻辑
  3. 它必须自行向整个系统提供这个实例(对外提供实例化方法
优点
1.由于在内存中只有一个实例,减少了内存开支,特别是一个对象要频繁创建、销毁时,且创建或销毁时性能又无法优化,单例模式的优势就非常明显;
2.当一个对象的产生需要比较多的资源时,如读取配置、产生其他依赖对象时,则可以通过在应用启动时直接产生一个单例对象,然后用永久驻留内存的方式来解决;(在JavaEE中采用单例模式时需要注意JVM垃圾回收机制);
3.单例模式可以避免对资源的多重占用,例如一个写文件动作,由于只有一个实例存在内存中,避免对同一个资源文件的同时写操作;
4.单例模式可以在系统设置全局的访问点,优化和共享资源访问,例如可以设计一个单例类,负责所有数据表的映射处理;
缺点
1.单例模式一般没有接口,扩展很困难,若要扩展,除了修改代码基本上没有第二种途径可以实现。
2.单例模式为什么不能增加接口呢? 因为接口对单例模式是没有任何意义的,它要求“自行实例化”,并且提供单一实例、接口或抽象类是不可能被实例化的。
3.单例模式对测试是不利的:在并行开发环境中,如果单例模式没有完成,是不能进行测试的,没有接口也不能使用mock的方式虚拟一个对象。
4.单例模式与单一职责原则有冲突:一个类应该只实现一个逻辑,而不关心它是否是单例的,是不是要单例取决于环境,单例模式把“要单例”和业务逻辑融合在一个类中。

适用场景

1.要求生成唯一序列号的环境;
2.在整个项目中需要一个共享访问点或共享数据,例如一个Web页面上的计数器,可以不用把每次刷新都记录到数据库中,使用单例模式保持计数器的值,并确保是线程安全的;
3.创建一个对象需要消耗的资源过多, 如要访问IO和数据库等资源;
4.需要定义大量的静态常量和静态方法(如工具类)的环境,可以采用单例模式(也可以直接声明为static的方式)
​
J2EE标准中ServletContext、ServletContextConfig等,
Spring框架应用中的ApplicationContext、数据库连接池等也都是单例形式。
懒汉式:在真正需要使用时才去创建该对象;被外部类调用的时候,内部类才会加载;
       在开发中对内存要求非常高时,使用懒汉式
饿汉式:在类加载时已经创建好该对象,等待被程序使用;
       对内存要求不高时,使用饿汉式,简单不易错,且没有并发安全和性能问题;
​
懒汉模式1(线程不安全):#并发的情况下,若两个线程同时判断,会变成双例;
懒汉模式2(在方法上加锁):#虽然规避了线程安全问题,但每次获取对象前都要先获取锁,并发性能差
懒汉模式3(双重检查锁):#但还存在:指令重排序问题
懒汉模式4(指令重排):#其指令执行顺序可能会发生变化,在声明对象时,加 volatile 关键字修饰变量;
懒汉模式5(内部类):#内部类一定是要在方法调用之前初始化,巧妙地避免了线程安全问题
饿汉模式(线程安全):#因为在类加载时已经创建好了对象,只用调用就可以了,所以是线程安全的
#上面的单例模式对于创建来说都是不安全的,因为用反射和序列化破坏掉;
反射破坏单例模式:#
枚举式单例模式:#使用枚举可以规避反射破坏单例

2.1.1 懒汉模式1(线程不安全)

class LazySingletonV1 {
    private static LazySingletonV1 lazySingletonV1;//声明对象
    private LazySingletonV1() {//限制产生多个对象
        System.out.println(Thread.currentThread().getName());//测试打印线程名称
    }
    /** 一
     * 创建对象方法时在程序使用对象前,先判断该对象是否实例化,若实例化直接返回该对象,否则先执行实例化操作;
     * 但是,在并发的情况下,若两个线程同时判断,则会变成双例,则需要解决线程安全问题
     */
    public static LazySingletonV1 getLazySingletonV1() {
        if (lazySingletonV1 == null) {
            lazySingletonV1 = new LazySingletonV1();
        }
        return lazySingletonV1;
    }
​
    //类中其他方法,尽量是static的
    public static void doSomething() {}
​
    public static void main(String[] args) {
        for (int i = 0; i < 10 ; i++) {
            new Thread(LazySingletonV1::getLazySingletonV1).start();
        }
    }
}

2.1.2 懒汉模式2(在方法上加锁)

class LazySingletonV2 {
    private static LazySingletonV2 lazySingletonV2;//声明对象
    private LazySingletonV2() {}//限制产生多个对象
    /** 二
     * 解决线程安全问题:在方法上加锁,或在对象上加锁
     * 虽然规避了线程安全问题,但又:每次获取对象前都要先获取锁,并发性能差
     */
    public static synchronized LazySingletonV2 getLazySingletonV2a() {
        if (lazySingletonV2 == null) {
            lazySingletonV2 = new LazySingletonV2();
        }
        return lazySingletonV2;
    }
/** 或者 **/
    public static LazySingletonV2 getLazySingletonV2b() {
        synchronized (LazySingletonV2.class) {
            if (lazySingletonV2 == null) {
                lazySingletonV2 = new LazySingletonV2();
            }
        }
        return lazySingletonV2;
    }
​
    //类中其他方法,尽量是static的
    public static void doSomething() {}
}

2.1.3 懒汉模式3(双重检查锁)

class LazySingletonV3 {
    private static LazySingletonV3 lazySingletonV3;//声明对象
    private LazySingletonV3() {}//限制产生多个对象
    /** 三
     * 解决并发安全 + 性能低效,也称为:Double Check + Lock [双检查锁]
     * 但还存在:指令重排序问题
     */
    public static LazySingletonV3 getLazySingletonV3() {
        if (lazySingletonV3 == null) {        //只有在第一次未实例化时,线程才会抢锁
            synchronized (LazySingletonV3.class) {
                if (lazySingletonV3 == null) {
                    lazySingletonV3 = new LazySingletonV3();
                }
            }
        }
        return lazySingletonV3;
    }
​
    //类中其他方法,尽量是static的
    public static void doSomething() {}
}

2.1.4 懒汉模式4(指令重排)

class LazySingletonV4 {
    /**
     * 在上面的基础上
     * 指令重排问题:在声明对象时,加 volatile 关键字修饰变量
     * 可保证其指令执行顺序不会发生变化,在多线程环境下就不会发生NPE异常
     */
    //声明对象
    private static volatile LazySingletonV4 lazySingletonV4;
    //限制产生多个对象
    private LazySingletonV4() {}
​
    public static LazySingletonV4 getLazySingletonV4() {
        if (lazySingletonV4 == null) {        //只有在第一次未实例化时,线程才会抢锁
            synchronized (LazySingletonV4.class) {
                if (lazySingletonV4 == null) {
                    lazySingletonV4 = new LazySingletonV4();
                }
            }
        }
        return lazySingletonV4;
    }
​
    //类中其他方法,尽量是static的
    public static void doSomething() {}
}

2.1.5 懒汉模式5(内部类)

class LazySingletonV5 {
    /**
     * 这种模式兼顾饿汉式单例模式的内存浪费问题 和 synchronized性能问题
     * 内部类一定是要在方法调用之前初始化,巧妙地避免了线程安全问题。
     */
    //在使用LazySingletonV5的时候,会默认先初始化内部类
    //如果没有使用,内部类则不加载
    private LazySingletonV5() {
        System.out.println(Thread.currentThread().getName());//测试打印线程名称
    }
    //每一个关键字都不是多余的,static是为了使单例的空间共享,保证这个方法不被重写、重载
    public static LazySingletonV5 getLazySingletonV5() {
        return LazyHolder.LAZY;//在返回结果前,一定会先加载内部类
    }
    //默认不加载
    private static class LazyHolder {
        private static final LazySingletonV5 LAZY = new LazySingletonV5();
    }
​
    //类中其他方法,尽量是static的
    public static void doSomething() {}
​
    public static void main(String[] args) {
        for (int i = 0; i < 10 ; i++) {
            new Thread(LazySingletonV5::getLazySingletonV5).start();
        }
    }
}

2.1.6 饿汉模式(线程安全)

class HungrySingleton {
    //在类加载时创建该对象
    private static final HungrySingleton hungrySingleton = new HungrySingleton();
    //限制产生多个对象
    private HungrySingleton() {}
    //通过该方法获取实例对象
    public static HungrySingleton getHungrySingleton() {
        return hungrySingleton;
    }
  
    //类中其他方法,尽量是static的
    public static void doSomething() {}
}

2.1.7 反射破坏单例模式

上面介绍的单例模式的构造方法除了加上private关键字,没有做任何的处理。如果我们使用反射来调用其构造方法,再调用getInstance()方法,应该有两个不同的实例。
//反射破坏单例模式
class ReflectionBreakSingleton {
    public static void main(String[] args) {
        try {
            //先正常获取单例对象
            LazySingletonV4 instance = LazySingletonV4.getLazySingletonV4();
​
            //进行破坏,获得空参构造器
            Constructor<LazySingletonV4> constructor = LazySingletonV4.class.getDeclaredConstructor();
            //强制访问
            constructor.setAccessible(true);
            //暴力初始化
            LazySingletonV4 instance2 = constructor.newInstance();
​
            System.out.println(instance == instance2);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
}
运行结果如下:
false
​
显然,创建了两个不同的实例,我们可以在其构造方法中做一些限制,避免重复创建

2.1.9 枚举式单例模式

public enum EnumSingleton {
    INSTANCE;
​
    //在枚举类中申明一个新的成员变量,才能实现扩展
    private Object data;
​
    public void setData(Object data) {
        this.data = data;
    }
​
    public Object getData() {
        return data;
    }
​
    public static EnumSingleton getInstance(){
        return INSTANCE;
    }
}

枚举类型其实通过类名和类对象找到唯一一个枚举对象。因此,枚举对象不可能被类加载器加载多次。

那么反射是否能破坏枚举式单例模式呢?

class ReflectionBreakEnumSingleton{
    public static void main(String[] args) {
        try {
            Constructor<EnumSingleton> declaredConstructor = EnumSingleton.class.getDeclaredConstructor();
            declaredConstructor.setAccessible(true);
            EnumSingleton instance1 = declaredConstructor.newInstance();
            System.out.println(instance1);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
运行结果:
java.lang.NoSuchMethodException: com.banksy.createModel.singleton.Enumsingleton.<init>()
结果中报的是java.lang.NoSuchMethodException异常,意思是没找到无参的构造方法。这时候,我们打开java.lang.Enum的源码,查看他的构造方法,只有一个protected类型的构造方法,代码如下:
protected Enum(String name, int ordinal) {
        this.name = name;
        this.ordinal = ordinal;
}
class ReflectionBreakEnumSingleton{
    public static void main(String[] args) {
        try {
            Constructor<EnumSingleton> declaredConstructor = EnumSingleton.class.getDeclaredConstructor(String.class, int.class);
            declaredConstructor.setAccessible(true);
            EnumSingleton instance1 = declaredConstructor.newInstance();
            System.out.println(instance1);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
输出:
java.lang.IllegalArgumentException: Cannot reflectively create enum objects
//这时的错误已经非常明显了,“Cannot reflectively create enum objects”,即不能用反射来创建枚举类型
进入Constructor的newInstance()方法源码可发现,在newInstance()方法调用的newInstanceWithCaller方法中调用的acquireConstructorAccessor做出了强制的判断,如果修饰符是Modifier.ENUM 枚举类型,直接抛出异常。
到此为止,我们已经非常清晰明了了吗?JDK枚举的语法特殊性及反射也为枚举保驾护航,让枚举单例模式成为一种比较优雅的实现。
​
···
if ((clazz.getModifiers() & Modifier.ENUM) != 0)
   throw new IllegalArgumentException("Cannot reflectively create enum objects");
···

2.2 原型模式

Specify the kinds of objects to create using a prototypical instance,and create new objects by copying this prototype.

用原型实例指定创建对象的种类, 并且通过拷贝这些原型创建新的对象。

优点
    性能优良【原型模式是在内存二进制流的拷贝, 要比直接new一个对象性能好很多】;
    逃避构造函数的约束【直接在内存中拷贝,构造函数是不会执行,减少了约束】;
缺点
    逃避构造函数的约束【直接在内存中拷贝,构造函数是不会执行,减少了约束】;
适用场景
    在并发场景下,线程1在使用对象1,在还未使用结束的情况下,线程2也需要使用对象1,就会更新对象1中的属性,此时就会出现线程安全问题;【解决方法】利用原型模式进行拷贝一个相同的对象进行使用;
​
扩展
    可对 原型模式 + 工厂模式 => 只new一次,剩下都拷贝就行了;
public class Prototype {
    public static void main(String[] args) {
        Date date = new Date();
        /**
         * 浅拷贝:不仅把值拷贝过来,把引用也拷贝过来
         **/
        System.out.println("=========================浅拷贝=======================");
        PrototypeShallowCopy shallowCopy = new PrototypeShallowCopy("浅拷贝......", date);
        PrototypeShallowCopy shallowCopy1 = shallowCopy.clone();
        System.out.println(shallowCopy.toString());
        System.out.println(shallowCopy1.toString());
        System.out.println("=========================修改date后=======================");
        date.setTime(8686767);
        System.out.println(shallowCopy.toString());
        System.out.println(shallowCopy1.toString());//克隆对象的date属性也变化了,说明两个对象同时指向同一个date
​
​
        System.out.println();
        System.out.println();
        /**
         * 深拷贝:把值拷贝,未拷贝引用
         **/
        Date date1 = new Date();
        System.out.println("=========================深拷贝=======================");
        PrototypeDeepCope deepCope = new PrototypeDeepCope("深拷贝......", date1);
        PrototypeDeepCope deepCope1 = deepCope.clone();
        System.out.println(deepCope.toString());
        System.out.println(deepCope1.toString());
        System.out.println("=========================修改date后=======================");
        date1.setTime(32525);
        System.out.println(deepCope.toString());//此时原始对象的Date属性发生改变
        System.out.println(deepCope1.toString());//克隆对象的Date未发生改变,说明两个对象指向不同的Date
    }
}
/**
 * 浅拷贝
 * @author banksy
 **/
public class PrototypeShallowCopy implements Cloneable {
    private String name;
    private Date createTime;
​
    @Override
    protected PrototypeShallowCopy clone() {
        PrototypeShallowCopy prototypeShallowCopy = null;
        try {
            prototypeShallowCopy = (PrototypeShallowCopy) super.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        return prototypeShallowCopy;
    }
​
······构造函数、getset、toString
}
/**
 * 深拷贝
 * @author banksy
 **/
public class PrototypeDeepCope implements Cloneable {
    private String name;
    private Date createTime;
​
    @Override
    protected PrototypeDeepCope clone() {
        PrototypeDeepCope prototypeDeepCope = null;
        try {
            prototypeDeepCope = (PrototypeDeepCope) super.clone();
            prototypeDeepCope.createTime = (Date) this.createTime.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        return prototypeDeepCope;
    }
​
······构造函数、getset、toString
}

2.3 工厂模式

2.3.1 简单工厂模式

简单工厂,又称静态工厂方法模式;根据参数但不同返回不同类的实例;
简单工厂模式专门定义一个类来负责创建其他类的实例,被创建的实例通常都是具有共同的父类;
简单工厂模式是指由一个工厂对象决定创建哪一种产品类的实例,但它不属于GoF的23种设计模式;
简单工厂模式适用于工厂类负责创建的对象较少的场景,且客户端只需要传入工厂类的参数,对于如何创建对象不需要关心;
简单工厂模式也有缺点:工厂类的职责相对过重,不易于扩展过于复杂的产品结构,横向扩展时,需要修改已有的代码;

设计模式-工厂模式1.png

//测试
class SimpleFactoryClient {
    public static void main(String[] args) {
        SimpleProduct simpleProduct = SimpleFactory.createProduct("A");
        simpleProduct.print();
    }
}
//工厂类
public class SimpleFactory {
    static SimpleProduct createProduct(String type) {
        if (type.equals("A"))
            return new SimpleProduct1();
        else
            return new SimpleProduct2();
    }
}
//抽象类
abstract class SimpleProduct {
    public abstract void print();
}
//实体类,产品1
class SimpleProduct1 extends SimpleProduct {
    @Override
    public void print() {
        System.out.println("产品1");
    }
}
//实体类,产品2
class SimpleProduct2 extends SimpleProduct {
    @Override
    public void print() {
        System.out.println("产品2");
    }
}

2.3.2 工厂方法模式

Define an interface for creating an object,but let subclasses decide which class to instantiate.Factory Method lets a class defer instantiation to subclasses.

定义一个用于创建对象的接口, 让子类决定实例化哪一个类。 工厂方法使一个类的实例化延迟到其子类。

优点:良好的封装性, 代码结构清晰。
     工厂方法模式的扩展性非常优秀。
     屏蔽产品类。
     工厂方法模式是典型的解耦框架。符合迪米特法则,依赖倒置原则,里氏替换原则
缺点:
  1、类的个数容易过多,增加复杂度
  2、增加了系统的抽象性和理解难度
适用场景:
  1、创建对象需要大量重复的代码。
  2、客户端(应用层)不依赖于产品类实例如何被创建、如何被实现等细节
  3、一个类通过其子类来指定创建哪个对象
​
横向扩展,不需要修改已有代码,只需添加工厂实体类和产品实体类即可

设计模式-工厂模式2.png

class MethodFactoryClient {
    public static void main(String[] args) {
        MethodFactoryA methodFactoryA = new MethodFactoryA();//去指定工厂,要产品
        methodFactoryA.getProduct().print();
    }
}
​
//抽象工厂类
abstract class MethodFactory {
    public abstract MethodProduct getProduct();
}
//实体类,工厂A
class MethodFactoryA extends MethodFactory {
    @Override
    public MethodProduct getProduct() {
        return new MethodProduct1();
    }
}
//实体类,工厂B
class MethodFactoryB extends MethodFactory {
    @Override
    public MethodProduct getProduct() {
        return new MethodProduct2();
    }
}
​
//抽象产品类
abstract class MethodProduct {
    public abstract void print();
}
//实体类,产品A
class MethodProduct1 extends MethodProduct {
    @Override
    public void print() {
        System.out.println("产品1");
    }
}
//实体类,产品B
class MethodProduct2 extends MethodProduct {
    @Override
    public void print() {
        System.out.println("产品2");
    }
}

2.3.3 抽象工厂模式

Provide an interface for creating families of related or dependent objects without specifying their concrete classes.

为创建一组相关或相互依赖的对象提供一个接口, 而且无须指定它们的具体类。

优点: 封装性,【每个产品的实现类不是高层模块要关心的,它关心的是接口,是抽象;
              它不关心对象是如何创建出来,关心由谁负责的,工厂类, 知道工厂类是谁,就能创建出需要的对象】
      产品族内的约束为非公开状态
缺点:
      横向扩展容易, 纵向扩展困难。
适用场景:
      一个对象族都有相同的约束, 则可以使用抽象工厂模式。

设计模式-工厂模式3.png

class AbstractFactoryClient {
    public static void main(String[] args) {
        AbstractFactoryA abstractFactoryA = new AbstractFactoryA();
        abstractFactoryA.abstractProduct1().print();//A工厂能造产品1,没有指定具体产品类
        abstractFactoryA.abstractProduct2().print();//A工厂能造产品2
    }
}
​
//任何工厂都能造,这两个产品
//抽象工厂,超级工厂
abstract class AbstractFactory {
    public abstract AbstractProduct1 abstractProduct1();//生产产品1
    public abstract AbstractProduct2 abstractProduct2();//生产产品2
}
//抽象类,产品1
abstract class AbstractProduct1 {
    public abstract void print();
}
//抽象类,产品2
abstract class AbstractProduct2 {
    public abstract void print();
}
​
//实体类,工厂A
class AbstractFactoryA extends AbstractFactory {
    @Override
    public AbstractProduct1 abstractProduct1() {
        return new AbstractProductA1();
    }
​
    @Override
    public AbstractProduct2 abstractProduct2() {
        return new AbstractProductA2();
    }
}
//实体类,工厂B
class AbstractFactoryB extends AbstractFactory {
    @Override
    public AbstractProduct1 abstractProduct1() {
        return new AbstractProductB1();
    }
​
    @Override
    public AbstractProduct2 abstractProduct2() {
        return new AbstractProductB2();
    }
}
​
//实体类,工厂A产品1
class AbstractProductA1 extends AbstractProduct1 {
    @Override
    public void print() {
        System.out.println("A工厂产品1");
    }
}
//实体类,工厂B产品1
class AbstractProductB1 extends AbstractProduct1 {
    @Override
    public void print() {
        System.out.println("B工厂产品1");
    }
}
//实体类,工厂A产品2
class AbstractProductA2 extends AbstractProduct2 {
    @Override
    public void print() {
        System.out.println("A工厂产品2");
    }
}
//实体类,工厂B产品2
class AbstractProductB2 extends AbstractProduct2 {
    @Override
    public void print() {
        System.out.println("B工厂产品2");
    }
}

2.4 建造者模式

Separate the construction of a complex object from its representation so that the same construction process can create different representations.

将一个复杂对象的构建与它的表示分离, 使得同样的构建过程可以创建不同的表示。

优点:封装性【不必须知道每一个具体的模型内部是如何实现的】;
     建造者独立、容易扩展;
     便于控制细节风险【具体的建造者是独立的, 因此可以对建造过程逐步细化,而不会对其他的模块产生任何影响】;
适用场景:
     相同方法,不同执行顺序,产生不同事件结果;
     多个部件或零件, 都可以装配到一个对象中, 但是产生的运行结果又不相同;
     产品类非常复杂, 或者产品类中的调用顺序不同产生了不同的效能;
注意:建造者模式关注的是顺序,这是和工厂模式最大的不同

设计模式-建造者模式.png

public class BuilderClient {
    public static void main(String[] args) {
        Director director = new Director();
        Product product = director.getProduct();
        product.doSomething();
    }
}
​
//导演类:起到封装的作用,避免高层模块深入到建造者内部的实现类
class Director {
    private AbstractBuilder concreteBuilder = new ConcreteBuilder();
    public Product getProduct() {
        concreteBuilder.setPart();
        return concreteBuilder.buildProduct();
    }
}
​
//抽象建造者
abstract class AbstractBuilder {
    //设置产品的不同部分,以获得不同的产品
    public abstract void setPart();
    //建造产品
    public abstract Product buildProduct();
}
​
//具体建造者
class ConcreteBuilder extends AbstractBuilder {
    private Product product = new Product();
​
    @Override
    public void setPart() {
​
    }
​
    @Override
    public Product buildProduct() {
        return product;
    }
}
​
//产品类
class Product {
    public void doSomething() {
        System.out.println("产品类......");
    }
}