设计模式摘要

最近学习了设计模式相关的内容,打算对设计模式相关的知识点分批做一下系统的总结。本节将设计模式的基本分类和六大原则列出,方便后续内容的开展。

设计模式分类

  • 创建型模式,共五种:工厂方法模式、抽象工厂模式、单例模式、建造者模式、原型模式。
  • 结构型模式,共七种:适配器模式、装饰器模式、代理模式、外观模式、桥接模式、组合模式、享元模式。
  • 行为型模式,共十一种:策略模式、模板方法模式、观察者模式、迭代子模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式。

设计模式的六大原则

  • 开闭原则(Open Close Principle)

    开闭原则就是说对扩展开放,对修改关闭。软件实体应尽量在不修改原有代码的情况下进行拓展。例如:为了使程序的扩展性好,易于维护和升级,我们需要使用接口和抽象类。

  • 里氏代换原则(Liskov Substitution Principle)

    任何基类可以出现的地方,子类一定可以出现。 LSP 是继承复用的基石,只有当衍生类可以替换掉基类,软件单位的功能不受到影响时,基类才能真正被复用,而衍生类也能够在基类的基础上增加新的行为。

  • 依赖倒转原则(Dependence Inversion Principle)

    面向接口编程,依赖于抽象而不依赖于具体,例如 spring 中的依赖注入。

  • 接口隔离原则(Interface Segregation Principle)

    使用多个隔离的接口,比使用单个接口要好。降低依赖,降低耦合。

  • 迪米特法则(最少知道原则)(Demeter Principle)

    一个实体应当尽量少的与其他实体之间发生相互作用,使得系统功能模块相对独立。

  • 单一职责原则(Single Responsibility Principle, SPR)

    一个类只负责一个功能领域中相应的职责,或者可以定义为:就一个类而言,应该只有一个引起它变化的原因。

单例模式

什么是单例

一个类有且仅有一个实例,并提供一个访问它的全局访问点。

适用场景

单例模式只允许创建一个对象,因此节省内存,加快对象访问速度,因此对象适合在需要被共用的场合使用。

  1. 需要频繁实例化然后销毁的对象。
  2. 创建对象时耗时过多或者耗资源过多,但又经常用到的对象。
  3. 有状态的工具类对象。
  4. 频繁访问数据库或文件的对象。

应用场景

  1. Windows 的任务管理器、回收站、文件系统是典型的单例应用。在整个系统运行过程中,一直维护着仅有的一个实例。
  2. 应用程序的日志应用,一般都采用单例模式实现,这一般是由于共享的日志文件一直处于打开状态,因为只能有一个实例去操作,否则内容不好追加。
  3. Web 应用的配置对象的读取,一般也应用单例模式,这个是由于配置文件是共享的资源。
  4. 多线程的线程池的设计一般也是采用单例模式,这是由于线程池要方便对池中的线程进行控制。

单例优缺点

优点:

  1. 确保单例类仅有一个实例
  2. 避免对共享资源的多重占用
  3. 提供了对唯一实例的受控访问
  4. 节约系统资源

缺点:

  1. 不适用于变化的对象,如果同一类型的对象总是要在不同的用例场景发生变化
  2. 实现方式上比较封闭,扩展困难
  3. 复杂功能单例类的职责过重,在一定程度上违背了“单一职责原则”
  4. 滥用单例将带来一些负面问题,如多个对象共享一个单例连接池,容易造成连接池溢出

单例常见创建方式

  • 饿汉式:类初始化时,会立即加载该对象,线程天生安全,调用效率高。
  • 懒汉式:类初始化时,不会初始化该对象,真正需要使用的时候才会创建该对象,具备懒加载功能,但存在安全问题。
  • 双重检测锁方式:对懒加载添加校验,兼具线程安全和高效率。
  • 静态内部方式:结合了懒汉式和饿汉式各自的优点,真正需要对象的时候才会加载,并且加载是线程安全的。
  • 枚举单例:使用枚举实现单例模式 优点:实现简单、调用效率高,枚举本身就是单例。

饿汉式

//饿汉式
public class Singleton01 {
    // 类初始化时,会立即加载该对象,线程天生安全,调用效率高
    private static Singleton01 singleton01 = new Singleton01();
    private Singleton01() {
        System.out.println("初始化");
    }
    public static Singleton01 getInstance() {
        System.out.println("getInstance");
        return singleton01;
    }


    public static void main(String[] args) {

        Singleton01 s1 = Singleton01.getInstance();
        System.out.println("实例1:"+s1);
        Singleton01 s2 = Singleton01.getInstance();
        System.out.println("实例2:"+s2);
        System.out.println("实例1 == 实例2:" + (s1 == s2));
    }
}

输出结果如下:

初始化
getInstance
实例1:singleton.Singleton01@355da254
getInstance
实例2:singleton.Singleton01@355da254
实例1 == 实例2:true

懒汉式

//懒汉式
public class Singleton02 {
    //类初始化时,不会初始化该对象,真正需要使用的时候才会创建该对象。
    private static Singleton02 singleton02 = null;
    private Singleton02() {
        System.out.println("初始化");
    }
    public static Singleton02 getInstance() {
        if(singleton02 == null){
            singleton02 = new Singleton02();
        }
        System.out.println("getInstance");
        return singleton02;
    }


    public static void main(String[] args) {

        Singleton02 s1 = Singleton02.getInstance();
        System.out.println("实例1:"+s1);
        Singleton02 s2 = Singleton02.getInstance();
        System.out.println("实例2:"+s2);
        System.out.println("实例1 == 实例2:" + (s1 == s2));
    }
}

输出结果如下:

初始化
getInstance
实例1:singleton.Singleton02@355da254
getInstance
实例2:singleton.Singleton02@355da254
实例1 == 实例2:true

双重检测锁

//双重检测锁
public class Singleton03 {
    //类初始化时,不会初始化该对象,真正需要使用的时候才会创建该对象。
    private static Singleton03 singleton03 = null;
    private Singleton03() {
        System.out.println("初始化");
    }
    public static Singleton03 getInstance() {
        //第一次判断,假设有多个线程,如果singleton03没有被实例化,那么就会到下一步获取锁,只有一个能获取到,
        //如果已经实例化,那么直接返回了,减少除了初始化时之外的所有锁获取等待过程
        if(singleton03 == null){
            synchronized (Singleton03.class){
                //第二次判断是因为假设有两个线程A、B,两个同时通过了第一个if,然后A获取了锁,进入然后判断singleton03是null,他就实例化了singleton03,然后他出了锁,
                //这时候线程B经过等待A释放的锁,B获取锁了,如果没有第二个判断,那么他还是会去new Singleton03(),再创建一个实例,所以为了防止这种情况,需要第二次判断
               if(singleton03 == null){
                    singleton03 = new Singleton03();
                }
            }
        }
        System.out.println("getInstance");
        return singleton03;
    }


    public static void main(String[] args) {

        Singleton03 s1 = Singleton03.getInstance();
        System.out.println("实例1:"+s1);
        Singleton03 s2 = Singleton03.getInstance();
        System.out.println("实例2:"+s2);
        System.out.println("实例1 == 实例2:" + (s1 == s2));
    }
}

输出结果如下:

初始化
getInstance
实例1:singleton.Singleton03@355da254
getInstance
实例2:singleton.Singleton03@355da254
实例1 == 实例2:true

静态内部类

//静态内部类
public class Singleton04 {
    private Singleton04() {
        System.out.println("初始化");
    }
    public static class InnerClass {
        //加载一个类时,其内部类不会同时被加载。一个类被加载,当且仅当其某个静态成员(静态域、构造器、静态方法等)被调用时发生。
        //并且静态内部类只能同时有一个线程去初始化它,其他的被阻塞,如果一个线程初始化了这个静态内部类,那么其他的就不会再去初始化了。
        private static final Singleton04 singleton04 = new Singleton04();
    }
    public static Singleton04 getInstance() {
        System.out.println("getInstance");
        return InnerClass.singleton04;
    }


    public static void main(String[] args) {

        Singleton04 s1 = Singleton04.getInstance();
        System.out.println("实例1:"+s1);
        Singleton04 s2 = Singleton04.getInstance();
        System.out.println("实例2:"+s2);
        System.out.println("实例1 == 实例2:" + (s1 == s2));
    }
}

输出结果如下:

getInstance
初始化
实例1:singleton.Singleton04@355da254
getInstance
实例2:singleton.Singleton04@355da254
实例1 == 实例2:true

枚举方式

枚举本身是单例的,一般用于项目中定义常量。由 jvm 从根本上提供保障。

//枚举方式
public class Singleton05 {

    public static Singleton05 getInstance(){
        return Singleton05Enum.INSTANCE.getInstance();
    }

    private enum Singleton05Enum {
        INSTANCE;
        private Singleton05 singleton05;
        private Singleton05Enum() {
            singleton05 = new Singleton05();
            System.out.println("初始化");
        }
        public Singleton05 getInstance() {
            return singleton05;
        }
    }


    public static void main(String[] args) {

        Singleton05 s1 = Singleton05.getInstance();
        System.out.println("实例1:"+s1);
        Singleton05 s2 = Singleton05.getInstance();
        System.out.println("实例2:"+s2);
        System.out.println("实例1 == 实例2:" + (s1 == s2));
    }
}

输出结果如下:

初始化
实例1:singleton.Singleton05@355da254
实例2:singleton.Singleton05@355da254
实例1 == 实例2:true

破坏单例模式的三种方式

  • 反射
  • 序列化
  • 克隆

反射

虽然构造方法已私有化,但通过反射机制使用 setAccessible(true)方法,构造方法也是可以被调用。

以双重检测锁为例:

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;

//双重检测锁
public class Singleton03 {
    //类初始化时,不会初始化该对象,真正需要使用的时候才会创建该对象。
    private static Singleton03 singleton03 = null;
    private Singleton03() {
        System.out.println("初始化");
    }
    public static Singleton03 getInstance() {
        //第一次判断,假设有多个线程,如果singleton03没有被实例化,那么就会到下一步获取锁,只有一个能获取到,
        //如果已经实例化,那么直接返回了,减少除了初始化时之外的所有锁获取等待过程
        if(singleton03 == null){
            synchronized (Singleton03.class){
                //第二次判断是因为假设有两个线程A、B,两个同时通过了第一个if,然后A获取了锁,进入然后判断singleton03是null,他就实例化了singleton03,然后他出了锁,
                //这时候线程B经过等待A释放的锁,B获取锁了,如果没有第二个判断,那么他还是会去new Singleton03(),再创建一个实例,所以为了防止这种情况,需要第二次判断
                if(singleton03 == null){
                    singleton03 = new Singleton03();
                }
            }
        }
        System.out.println("getInstance");
        return singleton03;
    }


    public static void main(String[] args) throws Exception {

        //通过getInstance()获取
        Singleton03 singleton = Singleton03.getInstance();
        System.out.println("正常实例化:"+singleton);
        //通过反射获取
        Constructor<Singleton03> constructor = Singleton03.class.getDeclaredConstructor();
        constructor.setAccessible(true);
        Singleton03 reflex = constructor.newInstance();
        System.out.println("反射实例化:"+reflex);
        System.out.println("正常实例化 == 反射实例化:" + (singleton == reflex));
    }
}

输出结果如下:

初始化
getInstance
正常实例化:singleton.Singleton03@355da254
初始化
反射实例化:singleton.Singleton03@4dc63996
正常实例化 == 反射实例化:false

序列化

import java.io.*;

//双重检测锁
public class Singleton03 implements Serializable {
    //类初始化时,不会初始化该对象,真正需要使用的时候才会创建该对象。
    private static Singleton03 singleton03 = null;
    private Singleton03() {
        System.out.println("初始化");
    }
    public static Singleton03 getInstance() {
        //第一次判断,假设有多个线程,如果singleton03没有被实例化,那么就会到下一步获取锁,只有一个能获取到,
        //如果已经实例化,那么直接返回了,减少除了初始化时之外的所有锁获取等待过程
        if(singleton03 == null){
            synchronized (Singleton03.class){
                //第二次判断是因为假设有两个线程A、B,两个同时通过了第一个if,然后A获取了锁,进入然后判断singleton03是null,他就实例化了singleton03,然后他出了锁,
                //这时候线程B经过等待A释放的锁,B获取锁了,如果没有第二个判断,那么他还是会去new Singleton03(),再创建一个实例,所以为了防止这种情况,需要第二次判断
                if(singleton03 == null){
                    singleton03 = new Singleton03();
                }
            }
        }
        System.out.println("getInstance");
        return singleton03;
    }


    public static void main(String[] args) throws Exception {

        //通过getInstance()获取
        Singleton03 singleton = Singleton03.getInstance();
        System.out.println("正常实例化:"+singleton);
        //通过序列化,反序列化获取
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(bos);
        oos.writeObject(Singleton03.getInstance());
        ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
        ObjectInputStream ois = new ObjectInputStream(bis);
        Singleton03 serialize = (Singleton03) ois.readObject();
        ois.close();
        bis.close();
        oos.close();
        bos.close();
        System.out.println("序列化后实例化:"+serialize);
        System.out.println("正常实例化 == 序列化实例化:" + (singleton == serialize));
    }
}

输出结果如下:

初始化
getInstance
正常实例化:singleton.Singleton03@355da254
getInstance
序列化后实例化:singleton.Singleton03@5b6f7412
正常实例化 == 序列化实例化:false

克隆

//双重检测锁
public class Singleton03 implements Cloneable {
    //类初始化时,不会初始化该对象,真正需要使用的时候才会创建该对象。
    private static Singleton03 singleton03 = null;
    private Singleton03() {
        System.out.println("初始化");
    }
    public static Singleton03 getInstance() {
        //第一次判断,假设有多个线程,如果singleton03没有被实例化,那么就会到下一步获取锁,只有一个能获取到,
        //如果已经实例化,那么直接返回了,减少除了初始化时之外的所有锁获取等待过程
        if(singleton03 == null){
            synchronized (Singleton03.class){
                //第二次判断是因为假设有两个线程A、B,两个同时通过了第一个if,然后A获取了锁,进入然后判断singleton03是null,他就实例化了singleton03,然后他出了锁,
                //这时候线程B经过等待A释放的锁,B获取锁了,如果没有第二个判断,那么他还是会去new Singleton03(),再创建一个实例,所以为了防止这种情况,需要第二次判断
                if(singleton03 == null){
                    singleton03 = new Singleton03();
                }
            }
        }
        System.out.println("getInstance");
        return singleton03;
    }

    public static void main(String[] args) throws Exception {

        //通过getInstance()获取
        Singleton03 singleton = Singleton03.getInstance();
        System.out.println("正常实例化:"+singleton);
        //通过克隆获取
        Singleton03 clone = (Singleton03) Singleton03.getInstance().clone();
        System.out.println("克隆实例化:"+clone);
        System.out.println("正常实例化 == 克隆实例化:" + (singleton == clone));
    }
}

输出结果如下:

初始化
getInstance
正常实例化:singleton.Singleton03@355da254
getInstance
克隆实例化:singleton.Singleton03@4dc63996
正常实例化 == 克隆实例化:false

如何防止反射、克隆、序列化对单例模式的破坏

反射

设置一个全局变量开关默认为开启状态,当第一次加载时将其状态更改为关闭状态。

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;

//双重检测锁
public class Singleton03 {
    //类初始化时,不会初始化该对象,真正需要使用的时候才会创建该对象。
    private static Singleton03 singleton03 = null;
    private static boolean flag = true;
        private Singleton03() {
            if (flag) {
                synchronized (Singleton03.class) {
            if (flag) {
              flag = false;
                       System.out.println("初始化");
            }
                }
            }else{
                throw new RuntimeException("已然被实例化一次,不能在实例化");
            }
    }
    public static Singleton03 getInstance() {
        //第一次判断,假设有多个线程,如果singleton03没有被实例化,那么就会到下一步获取锁,只有一个能获取到,
        //如果已经实例化,那么直接返回了,减少除了初始化时之外的所有锁获取等待过程
        if(singleton03 == null){
            synchronized (Singleton03.class){
                //第二次判断是因为假设有两个线程A、B,两个同时通过了第一个if,然后A获取了锁,进入然后判断singleton03是null,他就实例化了singleton03,然后他出了锁,
                //这时候线程B经过等待A释放的锁,B获取锁了,如果没有第二个判断,那么他还是会去new Singleton03(),再创建一个实例,所以为了防止这种情况,需要第二次判断
                if(singleton03 == null){
                    singleton03 = new Singleton03();
                }
            }
        }
        System.out.println("getInstance");
        return singleton03;
    }


    public static void main(String[] args) throws Exception {

        //通过getInstance()获取
        Singleton03 singleton = Singleton03.getInstance();
        System.out.println("正常实例化:"+singleton);
        //通过反射获取
        Constructor<Singleton03> constructor = Singleton03.class.getDeclaredConstructor();
        constructor.setAccessible(true);
        Singleton03 reflex = constructor.newInstance();
        System.out.println("反射实例化:"+reflex);
        System.out.println("正常实例化 == 反射实例化:" + (singleton == reflex));
    }
}

输出结果如下:

初始化
getInstance
正常实例化:singleton.Singleton03@355da254
Exception in thread "main" java.lang.reflect.InvocationTargetException
	at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
	at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
	at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
	at java.lang.reflect.Constructor.newInstance(Constructor.java:423)
	at singleton.Singleton03.main(Singleton03.java:48)
Caused by: java.lang.RuntimeException: 已然被实例化一次,不能在实例化
	at singleton.Singleton03.<init>(Singleton03.java:20)
	... 5 more

序列化

添加 readResolve(),返回 Object 对象。

通过查看 ObjectInputStream 原码(忽略查找步骤),找到核心代码如下:

private Object readOrdinaryObject(boolean unshared)
        throws IOException
    {
        ......

        try {
            //isInstantiable()方法在该类实现了serializable/externalizable接口时,返回true
            //当返回为true时,就会创建一个新的对象(newInstance()方法)
            obj = desc.isInstantiable() ? desc.newInstance() : null;
        } catch (Exception ex) {
            throw (IOException) new InvalidClassException(
                desc.forClass().getName(),
                "unable to create instance").initCause(ex);
        }

        ......
        //hasReadResolveMethod():如果表示的类是实现了serializable/externalizable的,并定义一个符合的readResolve方法则返回true,否则,返回false
        if (obj != null &&
            handles.lookupException(passHandle) == null &&
            desc.hasReadResolveMethod())
        {
            Object rep = desc.invokeReadResolve(obj);
            if (unshared && rep.getClass().isArray()) {
                rep = cloneArray(rep);
            }
            if (rep != obj) {
                //用readResolve()返回的对象替换新创建的对象
                handles.setObject(passHandle, obj = rep);
            }
        }

        return obj;
    }

由源码可知,我们可以添加 readResolve()方法,返回之前实例化的对象。

import java.io.*;

//双重检测锁
public class Singleton03 implements Serializable {
    //类初始化时,不会初始化该对象,真正需要使用的时候才会创建该对象。
    private static Singleton03 singleton03 = null;
    private Singleton03() {
        System.out.println("初始化");
    }
    public static Singleton03 getInstance() {
        //第一次判断,假设有多个线程,如果singleton03没有被实例化,那么就会到下一步获取锁,只有一个能获取到,
        //如果已经实例化,那么直接返回了,减少除了初始化时之外的所有锁获取等待过程
        if(singleton03 == null){
            synchronized (Singleton03.class){
                //第二次判断是因为假设有两个线程A、B,两个同时通过了第一个if,然后A获取了锁,进入然后判断singleton03是null,他就实例化了singleton03,然后他出了锁,
                //这时候线程B经过等待A释放的锁,B获取锁了,如果没有第二个判断,那么他还是会去new Singleton03(),再创建一个实例,所以为了防止这种情况,需要第二次判断
                if(singleton03 == null){
                    singleton03 = new Singleton03();
                }
            }
        }
        System.out.println("getInstance");
        return singleton03;
    }
    private Object readResolve() {
        return singleton03;
    }


    public static void main(String[] args) throws Exception {

        //通过getInstance()获取
        Singleton03 singleton = Singleton03.getInstance();
        System.out.println("正常实例化:"+singleton);
        //通过序列化,反序列化获取
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(bos);
        oos.writeObject(Singleton03.getInstance());
        ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
        ObjectInputStream ois = new ObjectInputStream(bis);
        Singleton03 serialize = (Singleton03) ois.readObject();
        ois.close();
        bis.close();
        oos.close();
        bos.close();
        System.out.println("序列化后实例化:"+serialize);
        System.out.println("正常实例化 == 序列化实例化:" + (singleton == serialize));
    }
}

输出结果如下:

初始化
getInstance
正常实例化:singleton.Singleton03@355da254
getInstance
序列化后实例化:singleton.Singleton03@355da254
正常实例化 == 序列化实例化:true

克隆

重写 clone(),直接返回单例对象。

实现 Cloneable 接口,尽管构造函数是私有,但还会创建一个对象。因为 clone 方法不会调用构造函数,会直接从内存中 copy 内存区域。所以可以重写 clone(),直接返回单例对象。

//双重检测锁
public class Singleton03 implements Cloneable {
    //类初始化时,不会初始化该对象,真正需要使用的时候才会创建该对象。
    private static Singleton03 singleton03 = null;
    private Singleton03() {
        System.out.println("初始化");
    }
    public static Singleton03 getInstance() {
        //第一次判断,假设有多个线程,如果singleton03没有被实例化,那么就会到下一步获取锁,只有一个能获取到,
        //如果已经实例化,那么直接返回了,减少除了初始化时之外的所有锁获取等待过程
        if(singleton03 == null){
            synchronized (Singleton03.class){
                //第二次判断是因为假设有两个线程A、B,两个同时通过了第一个if,然后A获取了锁,进入然后判断singleton03是null,他就实例化了singleton03,然后他出了锁,
                //这时候线程B经过等待A释放的锁,B获取锁了,如果没有第二个判断,那么他还是会去new Singleton03(),再创建一个实例,所以为了防止这种情况,需要第二次判断
                if(singleton03 == null){
                    singleton03 = new Singleton03();
                }
            }
        }
        System.out.println("getInstance");
        return singleton03;
    }
    @Override
    protected Singleton03 clone() {
        return singleton03;
    }

    public static void main(String[] args) throws Exception {

        //通过getInstance()获取
        Singleton03 singleton = Singleton03.getInstance();
        System.out.println("正常实例化:"+singleton);
        //通过克隆获取
        Singleton03 clone = (Singleton03) Singleton03.getInstance().clone();
        System.out.println("克隆实例化:"+clone);
        System.out.println("正常实例化 == 克隆实例化:" + (singleton == clone));
    }
}

输出结果如下:

初始化
getInstance
正常实例化:singleton.Singleton03@355da254
getInstance
克隆实例化:singleton.Singleton03@355da254
正常实例化 == 克隆实例化:true

开源项目应用实例

Spring 中对象的默认创建模式即为单例模式。在 Spring 配置文件中,配置 bean 时也可以设置单例或多例模式。

bean 配置

<bean id="springDemo" class="singleton.SpringSingleton" scope="singleton" />

Spring 样例类

public class SpringSingleton {
    public SpringSingleton(){
        System.out.println("初始化。。。");
    }
}

测试类

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.FileSystemXmlApplicationContext;

public class Test {

    public static void main(String[] args) {
        ApplicationContext context = new FileSystemXmlApplicationContext("applicationContext.xml");
        SpringSingleton demo1 = (SpringSingleton) context.getBean("springDemo");
        SpringSingleton demo2 = (SpringSingleton) context.getBean("springDemo");

        System.out.println(demo1);
        System.out.println(demo2);

    }
}

输出结果:

初始化。。。
singleton.SpringSingleton@53b32d7
singleton.SpringSingleton@53b32d7

修改 bean 配置:

<bean id="springDemo" class="singleton.SpringSingleton" scope="prototype" />

输出结果:

初始化。。。
初始化。。。
singleton.SpringSingleton@6d4b1c02
singleton.SpringSingleton@6093dd95

工厂模式

什么是工厂模式

实现了创建者和调用者分离,与通常的 new 对象方式相比,可以将对象属性设置放在工厂类中实现,而不是把众多的属性值放在构造方法内,实现了调用者和创建者之间的解耦。

工厂模式分类

简单工厂模式

简单工厂模式相当于是一个工厂中有各种产品,创建在一个类中,客户无需知道具体产品的名称,只需要知道产品类所对应的参数即可。但是工厂的职责过重,而且当类型过多、层次结构复杂时不利于系统的扩展维护。

import org.apache.commons.lang3.StringUtils;

interface CarInterface {
    void run();
}
class Benz implements CarInterface {
    public void run() {
        System.out.println("我是奔驰...");
    }
}
class BMW implements CarInterface {
    public void run() {
        System.out.println("我是宝马...");
    }
}
class CarFactory {
    public static CarInterface createCar(String name) {
        if (StringUtils.isEmpty(name)) {
            return null;
        }
        if(name.equals("奔驰")){
            return new Benz();
        }
        if(name.equals("宝马")){
            return new BMW();
        }
        return null;
    }
}
public class Car {
    public static void main(String[] args) {
        CarInterface benz  =CarFactory.createCar("奔驰");
        CarInterface bmw  =CarFactory.createCar("宝马");
        benz.run();
        bmw.run();
    }

输出结果如下:

我是奔驰...
我是宝马...

简单工厂的优点/缺点

  • 优点:简单工厂模式能够根据传入的参数信息,决定究竟应该创建哪个具体类的对象。实现了调用者和创建者之间的解耦。
  • 缺点:当类型过多、层次结构复杂时不利于系统的扩展维护,修改产品逻辑时,需要修改源代码。

工厂方法模式

在工厂方法模式中,核心的工厂类不再负责所有的产品的创建,而是将具体创建的工作交给子类去做。该核心类成为一个抽象工厂角色,仅负责给出具体工厂子类必须实现的接口,而不负责产品类被实例化的细节。只有具体的子类规定实例化时的细节。

interface CarInterface {
    void run();
}
interface CarFactory {
    CarInterface createCar();
}
class Benz implements CarInterface {
    public void run() {
        System.out.println("我是奔驰...");
    }
}
class BMW implements CarInterface {
    public void run() {
        System.out.println("我是宝马...");
    }
}
class BenzFactory implements CarFactory {
    @Override
    public CarInterface createCar() {
        return new Benz();
    }
}
class BMWFactory implements CarFactory {
    @Override
    public CarInterface createCar() {

        return new BMW();
    }
}

public class Car {
    public static void main(String[] args) {
        CarInterface benz  =new BenzFactory().createCar();
        CarInterface bmw  =new BMWFactory().createCar();
        benz.run();
        bmw.run();
    }
}

输出结果如下:

我是奔驰...
我是宝马...

工厂方法的优点/缺点

  • 优点:工厂方法模式克服了简单工厂模式的缺点。简单工厂模式的工厂类随着产品的增减需要修改源码,而工厂方法模式每个具体工厂子类只完成单一任务,代码简洁。具有非常良好的扩展性。
  • 缺点:当对工厂类进行修改时,每一个工厂子类都需要进行修改;每增加一种产品,也需要增加相应的工厂子类,会加大额外的开发量。

抽象工厂模式

抽象工厂简单地说是工厂的工厂,抽象工厂可以创建具体工厂,由具体工厂来产生具体产品。

//发动机
interface Engine {
    void run();
}
class EngineA implements Engine {
    public void run() {
        System.out.println("EngineA");
    }
}
class EngineB implements Engine {
    public void run() {
        System.out.println("EngineB");
    }
}

//座椅
interface Chair {
    void run();
}
class ChairA implements Chair{
    public void run() {
        System.out.println("ChairA");
    }

}
class ChairB implements Chair{
    public void run() {
        System.out.println("ChairB");
    }

}

interface CarFactory {
    // 创建发动机
    Engine createEngine();
    // 创建座椅
    Chair createChair();
}
class BenzFactory implements CarFactory  {
    public Engine createEngine() {

        return new EngineA();
    }
    public Chair createChair() {

        return new ChairA();
    }
}
class BMWFactory implements CarFactory  {
    public Engine createEngine() {

        return new EngineB();
    }
    public Chair createChair() {

        return new ChairB();
    }
}

public class Car {
    public static void main(String[] args) {

        System.out.println("---------奔驰---------");
        CarFactory benz = new BenzFactory();
        Engine engine = benz.createEngine();
        Chair chair = benz.createChair();
        engine.run();
        chair.run();
        System.out.println("---------宝马---------");
        CarFactory bmw = new BMWFactory();
        engine = bmw.createEngine();
        chair = bmw.createChair();
        engine.run();
        chair.run();
    }
}

输出结果如下:

---------奔驰---------
EngineA
ChairA
---------宝马---------
EngineB
ChairB

抽象工厂的优点/缺点

  • 优点:可以在类的内部对产品族进行约束。抽象工厂模式可以在类内部对产品族的关联关系进行定义和描述,封装了具体实现细节,按模板进行拼装,且增加了组装的便利性。
  • 缺点:产品族中需要增加一个新的产品,则几乎所有的工厂类都需要进行修改。

简单工厂、工厂方法、抽象工厂小结

  • 简单工厂 : 用来生产同一等级结构中的任意产品。
  • 工厂方法 :用来生产同一等级结构中的固定产品。
  • 抽象工厂 :用来生产不同产品族的全部产品。

开源项目应用实例

SpringBoot 启动的时候,除了加载主配置类,扫描启动类所在包下的所有组件,还会加载一系列 xxx-AutoConfiguration 自动配置类到容器中。这些配置类中就有许多根据配置文件内容,利用工厂模式创建组件的情况。以嵌入式 Tomcat 容器实例初始化为例:

EmbeddedWebServerFactoryCustomizerAutoConfiguration

@Configuration
@ConditionalOnWebApplication
@EnableConfigurationProperties({ServerProperties.class})
public class EmbeddedWebServerFactoryCustomizerAutoConfiguration {
    public EmbeddedWebServerFactoryCustomizerAutoConfiguration() {
    }
	......
	//初始化Tomcat
	@Configuration
    @ConditionalOnClass({Tomcat.class, UpgradeProtocol.class})
    public static class TomcatWebServerFactoryCustomizerConfiguration {
		public TomcatWebServerFactoryCustomizerConfiguration() {
        }
        @Bean
        public TomcatWebServerFactoryCustomizer tomcatWebServerFactoryCustomizer(Environment environment, ServerProperties serverProperties) {
		//实例化Tomcat工厂定制器
            return new TomcatWebServerFactoryCustomizer(environment, serverProperties);
        }
    }
	//初始化Undertow
	@Configuration
    @ConditionalOnClass({Undertow.class, SslClientAuthMode.class})
    public static class UndertowWebServerFactoryCustomizerConfiguration {
       ......
    }
	//初始化Jetty
    @Configuration
    @ConditionalOnClass({Server.class, Loader.class, WebAppContext.class}){
		......
    }
}

TomcatWebServerFactoryCustomizer

public class TomcatWebServerFactoryCustomizer implements WebServerFactoryCustomizer<ConfigurableTomcatWebServerFactory>, Ordered {
	......
	//定制Tomcat容器工厂
    public void customize(ConfigurableTomcatWebServerFactory factory) {
        ServerProperties properties = this.serverProperties;
        Tomcat tomcatProperties = properties.getTomcat();
        PropertyMapper propertyMapper = PropertyMapper.get();
        tomcatProperties.getClass();
        propertyMapper.from(tomcatProperties::getBasedir).whenNonNull().to(factory::setBaseDirectory);
        tomcatProperties.getClass();
       ......
    }
}

TomcatServletWebServerFactory

public class TomcatServletWebServerFactory extends AbstractServletWebServerFactory implements ConfigurableTomcatWebServerFactory, ResourceLoaderAware {
	......
	//实例化Tomcat容器
    public WebServer getWebServer(ServletContextInitializer... initializers) {
        Tomcat tomcat = new Tomcat();
        File baseDir = this.baseDirectory != null ? this.baseDirectory : this.createTempDir("tomcat");
        tomcat.setBaseDir(baseDir.getAbsolutePath());
        ......
        return this.getTomcatWebServer(tomcat);
    }
	......
}

建造者模式

什么是建造者模式

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

建造者模式通用组成

  • Builder:给出一个抽象接口,以规范产品对象的各个组成成分的建造。这个接口规定要实现复杂对象的哪些部分的创建,并不涉及具体的对象部件的创建。
  • ConcreteBuilder:实现 Builder 接口,针对不同的商业逻辑,具体化复杂对象的各部分的创建。 在建造过程完成后,提供产品的实例。
  • Director:调用具体建造者来创建复杂对象的各个部分,在指导者中不涉及具体产品的信息,只负责保证对象各部分完整创建或按某种顺序创建。
  • Product:要创建的复杂对象。

建造者适用场景

  • 初始化一个对象时,参数过多,或者很多参数具有默认值
  • 需要生成的产品对象有复杂的内部结构,但这些产品对象结构上具备共性

实际案例

这里仍以汽车制造为例。汽车零件包括:引擎、座椅、轮子。

Car 及组成

public class Car {
    private Chair chair;
    private Engine engine;
    private Wheel wheel;

    public Wheel getWheel() {
        return wheel;
    }

    public void setWheel(Wheel wheel) {
        this.wheel = wheel;
    }

    public Engine getEngine() {
        return engine;
    }

    public void setEngine(Engine engine) {
        this.engine = engine;
    }

    public Chair getChair() {
        return chair;
    }

    public void setChair(Chair chair) {
        this.chair = chair;
    }
}
//座椅
interface Chair {
    void run();
}
class ChairA implements Chair {
    public void run() {
        System.out.println("ChairA");
    }

}
class ChairB implements Chair {
    public void run() {
        System.out.println("ChairB");
    }

}

//发动机
interface Engine {
    void run();
}
class EngineA implements Engine {
    public void run() {
        System.out.println("EngineA");
    }
}
class EngineB implements Engine {
    public void run() {
        System.out.println("EngineB");
    }
}

//轮子
interface Wheel {
    void run();
}
class WheelA implements Wheel {
    public void run() {
        System.out.println("WheelA");
    }
}
class WheelB implements Wheel {
    public void run() {
        System.out.println("WheelB");
    }
}

Builder:

public interface Builder {
    void buildEngine();
    void buildChair();
    void buildWheel();
    Car buildCar();
}

ConcreteBuilder:

class BenzBuilder implements Builder{
    private Car car;
    public BenzBuilder(){
        car = new Car();
    }

    @Override
    public void buildEngine() {
        car.setEngine(new EngineA());
    }

    @Override
    public void buildChair() {
        car.setChair(new ChairA());
    }

    @Override
    public void buildWheel() {
        car.setWheel(new WheelA());
    }

    @Override
    public Car buildCar() {
        return car;
    }
}

class BMWBuilder implements Builder{
    private Car car;
    public BMWBuilder(){
        car = new Car();
    }

    @Override
    public void buildEngine() {
        car.setEngine(new EngineB());
    }

    @Override
    public void buildChair() {
        car.setChair(new ChairB());
    }

    @Override
    public void buildWheel() {
        car.setWheel(new WheelB());
    }

    @Override
    public Car buildCar() {
        return car;
    }
}

Director:

public class CarDirector {
    public Car constructCar(Builder builder) {
        builder.buildChair();
        builder.buildEngine();
        builder.buildWheel();
        return builder.buildCar();
    }

    public static void main(String[] args) {
        CarDirector carDirector = new CarDirector();
        System.out.println("-------奔驰-------");
        Car car = carDirector.constructCar(new BenzBuilder());
        car.getChair().run();
        car.getWheel().run();
        car.getEngine().run();

        System.out.println("-------宝马-------");
        car = carDirector.constructCar(new BMWBuilder());
        car.getChair().run();
        car.getWheel().run();
        car.getEngine().run();
    }
}

输出结果如下:

-------奔驰-------
ChairA
WheelA
EngineA
-------宝马-------
ChairB
WheelB
EngineB

建造者模式的优点/缺点

  • 优点:将一个复杂的对象的构建与它的表示分离,解耦;具体的建造者类之间相互独立,有利于系统的扩展。
  • 缺点:适用范围有限。如果各产品之间的组成结构、建造过程差异性很大,则不适合使用建造者模式。

建造者模式与抽象工厂模式的比较:

  • 与抽象工厂模式相比,建造者模式返回一个组装好的完整产品,而抽象工厂模式返回一系列相关产品组成的产品族 。
  • 在抽象工厂模式中,实例化工厂子类,然后调用工厂方法获取所需产品对象,而在建造者模式中,在指导者中不涉及具体产品的信息,只负责保证对象各部分完整创建或按某种顺序创建。

开源项目应用实例

Java 中的 StringBuilder 是典型的建造者模式的应用。Appendable 作为 Builder 角色规范了构建顺序及基础组成,AbstractStringBuilder 作为第一层 ConcreteBuilder 角色完成第一层产品封装(奔驰汽车),StringBuilder 最为第二层 ConcreteBuilder 角色完成最后一层产品封装(德国进口的奔驰汽车),Director 则由实际调用该类的方法担任。

Appendable

public interface Appendable {
	 Appendable append(CharSequence csq) throws IOException;
	 Appendable append(CharSequence csq, int start, int end) throws IOException;
	 Appendable append(char c) throws IOException;
}

AbstractStringBuilder

abstract class AbstractStringBuilder implements Appendable, CharSequence {
	......
		public AbstractStringBuilder append(String str) {
        if (str == null)
            return appendNull();
        int len = str.length();
        ensureCapacityInternal(count + len);
        str.getChars(0, len, value, count);
        count += len;
        return this;
    }
	public AbstractStringBuilder append(long l) {
        if (l == Long.MIN_VALUE) {
            append("-9223372036854775808");
            return this;
        }
        int appendedLength = (l < 0) ? Long.stringSize(-l) + 1
                                     : Long.stringSize(l);
        int spaceNeeded = count + appendedLength;
        ensureCapacityInternal(spaceNeeded);
        Long.getChars(l, spaceNeeded, value);
        count = spaceNeeded;
        return this;
    }
	......
}

StringBuilder

public final class StringBuilder
    extends AbstractStringBuilder
    implements java.io.Serializable, CharSequence
	{
		......
			@Override
    public StringBuilder append(String str) {
        super.append(str);
        return this;
    }
		@Override
    public StringBuilder append(long lng) {
        super.append(lng);
        return this;
    }
		......
	}

原型模式

什么是原型模式

用原型实例指定创建对象的种类,并通过拷贝这些原型创建新的对象。原型模型是直接从内存中直接复制对象实例,这个过程也就是我们称的“克隆”。被复制的实例就是我们所称的“原型”。原型模式多用于创建复杂的或者构造耗时的实例,因为这种情况下,复制一个已经存在的实例可使程序运行更高效,不需要执行构造方法,避免了初始化占有的时间和空间。

应用场景

  • 类初始化需要消化非常多的资源,这个资源包括数据、硬件资源等,通过原型拷贝避免这些消耗。
  • 通过 new 产生的一个对象需要非常繁琐的数据准备或者权限,这时可以使用原型模式。
  • 一个对象需要提供给其他对象访问,而且各个调用者可能都需要修改其值时,可以考虑使用原型模式拷贝多个对象供调用者使用,即保护性拷贝。Spring 框架中的多例就是使用原型。

原型模式组成

  1. 抽象原型(Prototype)角色。此角色定义了的具体原型类所需的实现的方法。类似定义一个可以被复制的文件的功能。Prototype 类需要具备以下两个条件:
  2. 实现 Cloneable 接口。在 java 语言有一个 Cloneable 接口,它的作用只有一个,就是在运行时通知虚拟机可以安全地在实现了此接口的类上使用 clone 方法。在 java 虚拟机中,只有实现了这个接口的类才可以被拷贝,否则在运行时会抛出 CloneNotSupportedException 异常。
  3. 重写 Object 类中的 clone 方法。Java 中,所有类的父类都是 Object 类,Object 类中有一个 clone 方法,作用是返回对象的一个拷贝,但是其作用域 protected 类型的,一般的类无法调用,因此 Prototype 类需要将 clone 方法的作用域修改为 public 类型。
  4. 客户(Client)角色:客户类提出创建对象的请求;类似使用复制粘贴的功能。
  5. 具体原型(Concrete Prototype)角色:实现抽象原型角色的克隆接口,实现复制功能。

实际案例

定义抽象原型

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

public class Prototype implements Cloneable {
    private Integer num;
    private String name;
    private Map<String ,Integer> scores;

    public Prototype(Integer num , String name , Map<String ,Integer> scores){
        this.num = num;
        this.name = name;
        this. scores = scores;
    }

    public Integer getNum() {
        return num;
    }

    public void setNum(Integer num) {
        this.num = num;
    }

    public String getName() {
        return name;
    }

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

    public Map<String, Integer> getScores() {
        return scores;
    }

    public void setScores(Map<String, Integer> scores) {
        this.scores = scores;
    }

    @Override
    public String toString() {
        return "Prototype{" +
                "num=" + num +
                ", name='" + name + '\'' +
                ", scores=" + scores +
                '}';
    }
}

定义具体原型

import java.util.Map;

public class FileConcretePrototype extends Prototype {
    public FileConcretePrototype(Integer num , String name , Map<String ,Integer> scores){
        super(num,name,scores);
    }

    @Override
    //深拷贝
    public Prototype clone() throws CloneNotSupportedException {
        Prototype file = null;
        //对八大基本类型的克隆
        file = (Prototype) super.clone();
        //对复杂引用类型的克隆
        file.scores = (Map<String, Integer>) ((HashMap) this.scores).clone();
        return file;
    }

    public void show(){
        System.out.println("-------文件--------");
        System.out.println("文件号:" + this.getNum());
        System.out.println("文件名:" + this.getName());
        System.out.println("文件内容:" + this.getScores());
    }

}

定义用户去模拟过程

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

public class Client {
    public static void main(String[] args) throws Exception {
        Integer num = 10;
        String name = "文件名";
        Map<String , Integer> scores = new HashMap<String, Integer>();
        scores.put("name1",1);
        scores.put("name",2);

        FileConcretePrototype file = new FileConcretePrototype(num,name,scores);
        FileConcretePrototype file1 = (FileConcretePrototype) file.clone();
        FileConcretePrototype file2 = (FileConcretePrototype) file.clone();

        file.show();
        file1.show();
        file2.show();
        System.out.println("file == file1 : "+(file.getScores() == file1.getScores()));
        System.out.println("file1 == file2 : "+(file1.getScores() == file2.getScores()));

    }
}

输出结果如下:

-------文件--------
文件号:10
文件名:文件名
文件内容:{name=2, name1=1}
-------文件--------
文件号:10
文件名:文件名
文件内容:{name=2, name1=1}
-------文件--------
文件号:10
文件名:文件名
文件内容:{name=2, name1=1}
file == file1 : false
file1 == file2 : false

将 FilePrototype 类中的 clone( )方法重写为如下:

@Override
    //浅拷贝
    public Prototype clone() throws CloneNotSupportedException {
        return (Prototype) super.clone();
    }

输出结果如下:

-------文件--------
文件号:10
文件名:文件名
文件内容:{name=2, name1=1}
-------文件--------
文件号:10
文件名:文件名
文件内容:{name=2, name1=1}
-------文件--------
文件号:10
文件名:文件名
文件内容:{name=2, name1=1}
file == file1 : true
file1 == file2 : true

原型模式分类

原型模式分为浅复制和深复制

  • 浅复制 —- 只是拷贝了基本类型的数据,而引用类型数据,复制后也是会发生引用,我们把这种拷贝叫做“(浅复制)浅拷贝”,换句话说,浅复制仅仅是指向被复制的内存地址,如果原地址中对象被改变了,那么浅复制出来的对象也会相应改变。
  • 深复制 —- 在计算机中开辟了一块新的内存地址用于存放复制的对象。

开源项目应用实例

netty 中的抽象类 AbstractBootstrap 类继承了 Cloneable 接口,在 AbstractBootstrap 的实现类 Bootstrap 和 ServerBootstrap 中重写了 clone() 方法,分别返回对各自已实例化的对象的克隆。

//AbstractBootstrap
public abstract class AbstractBootstrap<B extends AbstractBootstrap<B, C>, C extends Channel> implements Cloneable {
	......
		AbstractBootstrap(AbstractBootstrap<B, C> bootstrap) {
        this.group = bootstrap.group;
        this.channelFactory = bootstrap.channelFactory;
        this.handler = bootstrap.handler;
        this.localAddress = bootstrap.localAddress;
        synchronized(bootstrap.options) {
            this.options.putAll(bootstrap.options);
        }

        synchronized(bootstrap.attrs) {
            this.attrs.putAll(bootstrap.attrs);
        }
    }
	......
}

//ServerBootstrap
public final class ServerBootstrap extends AbstractBootstrap<ServerBootstrap, ServerChannel> {
	......
		private ServerBootstrap(ServerBootstrap bootstrap) {
        super(bootstrap);
        this.childGroup = bootstrap.childGroup;
        this.childHandler = bootstrap.childHandler;
        synchronized(bootstrap.childOptions) {
            this.childOptions.putAll(bootstrap.childOptions);
        }

        synchronized(bootstrap.childAttrs) {
            this.childAttrs.putAll(bootstrap.childAttrs);
        }
    }
	 public ServerBootstrap clone() {
        return new ServerBootstrap(this);
    }
	......
}

//Bootstrap
public final class Bootstrap extends AbstractBootstrap<Bootstrap, Channel> {
	......
	private Bootstrap(Bootstrap bootstrap) {
        super(bootstrap);
        this.remoteAddress = bootstrap.remoteAddress;
    }
	public Bootstrap clone() {
        return new Bootstrap(this);
    }
	......
}
Logo

瓜分20万奖金 获得内推名额 丰厚实物奖励 易参与易上手

更多推荐