1949 2018-03-16 2020-06-25

前言:站在巨人的肩膀上,学习设计模式。

一、概述

创建型模式(Creational Patterns)主要用于创建对象,GoF提供了5种创建型模式,分别是

  • 工厂方法模式(Factory Method)
  • 抽象工厂模式(Abstract Factory)
  • 建造者模式(Builder)
  • 原型模式(Prototype)
  • 单例模式(Singletion)

补充:GoF(Gang of Four),字面义为4个闹事的家伙,但其实指的是四位著名的软件工程学者。GoF共提出了23中设计模式。

二、简单工厂模式

1、概述

定义:根据传入的参数即可返回所需的对象,而不需要知道具体的类名

说明:根据提供给它的数据,返回几个可能类中的一个类的实例。通常它返回的类都有一个公共的父类和公共的方法。简单工厂模式不属于GoF模式,但使用的比较多。

2、类图

这种模式比较简单,类图如下

简单工厂模式

三、工厂方法模式

1、概述

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

说明:将某一类对象的创建过程封装在单独的类中,通过引入抽象层的方式,来使得对象的创建和使用更为灵活。

2、类图

工厂方法模式

四、抽象工厂模式

1、概述

定义:提供一个创建一系列相关或相互依赖对象的接口,而无须指定它们具体的类

说明:在一个类中可以创建多个不同类型的对象,这些对象所对应的类型都源于抽象层,使得系统具有极佳的扩展性和灵活性。

2、类图

抽象工厂模式

五、建造者模式

1、概述

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

说明:一步一步构造一个由多个部分组成的复杂对象。

六、原型模式

1、概述

定义:用原型实例指定创建对象的种类,并且通过复制这个原型来创建新的对象

说明:通过复制已有对象创建出相似的其他对象。

2、浅克隆和深克隆

原型模式的本质是对对象的复制(克隆),这里有两种方式,分别为浅克隆和深克隆。其中

  • 浅克隆:被复制对象的所有基本类型成员变量,都具有与原来的对象相同的值,而所有的对其他对象的引用仍然指向原来的对象
  • 深克隆:被复制对象的所有基本类型成员变量,都具有与原来的对象相同的值。那些引用其他对象的变量将指向被复制过的新对象,而不再是原有的那些被引用的对象

如下代码

public class Test1 implements Cloneable, Serializable {
    static class Inner implements Serializable {
        int value;
        public Inner(int value) {
            this.value = value;
        }
    }
    int number;
    String str;
    Inner inner;
    public Test1(int number, String str, Inner inner) {
        this.number = number;
        this.str = str;
        this.inner = inner;
    }
    @Override
    public Test1 clone() {
        Test1 test1 = null;
        try {
            test1 = (Test1) super.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        return test1;
    }
    public Test1 deepClone() {
        Test1 test1 = null;
        try{
            // 写入字节/对象输出流
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            ObjectOutputStream oos = new ObjectOutputStream(baos);
            oos.writeObject(this);
            // 用字节/对象输入流读取字节输出流
            ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
            ObjectInputStream ois = new ObjectInputStream(bais);
            test1 = (Test1)(ois.readObject());
        }catch (IOException | ClassNotFoundException e) {
            e.printStackTrace();
        }
        return test1;
    }
    private static void testClone() {
        Test1 a = new Test1(1, "11", new Inner(1));
        Test1 b = a.clone();
        System.out.println(b.number == a.number);// true
        System.out.println(b.str == a.str);//true
        System.out.println(b.inner == a.inner);// true
        a.number = 2;
        a.str = "22";// 等同于new String("22")
        a.inner.value = 2;
        System.out.println(b.number);// 1
        System.out.println(b.str);// 11
        System.out.println(b.inner.value);// 2 对a的改变,影响到b,说明两者指向同一对象
    }
    private static void testDeepClone() {
        Test1 a = new Test1(1, "11", new Inner(1));
        Test1 b = a.deepClone();
        System.out.println(b.number == a.number);// true
        System.out.println(b.str == a.str);// false
        System.out.println(b.inner == a.inner);// false
        a.number = 2;
        a.str = "22";// 等同于new String("22")
        a.inner.value = 2;
        System.out.println(b.number);// 1
        System.out.println(b.str);// 11
        System.out.println(b.inner.value);// 1 以上数据说明a的所有对象都是与b独立开的
    }
    public static void main(String[] args) {
        testClone();
        testDeepClone();
    }
}

七、单例模式

1、概述

定义:保证一个类仅有一个实例,并提供一个访问它的全局访问点

说明:控制系统中所创建的对象实例的个数。

2、基本单例

首次调用时,初始化单例,非线程安全。

public class Singleton {
    private static Singleton singleton;
    private Singleton() {
    }
    // 若两个线程同时进入,其中一个正在new Singleton,但另一线程仍感知singleton为null
    public static Singleton getSingleton() {
        if (singleton == null) {
            singleton = new Singleton();
        }
        return singleton;
    }
}

3、饿汉单例

类加载时,自动初始化单例,线程安全(推荐)。

public class Singleton {
    private static final Singleton singleton = new Singleton();
    private Singleton() {
    }
    public static Singleton getSingleton() {
        return singleton;
    }
}

4、懒汉单例

首次调用时,初始化单例,线程安全。在方法上加锁,保证多线程不会污染单例,但效率低。

public class Singleton {
    private static Singleton singleton;
    private Singleton() {
    }
    public synchronized static Singleton getSingleton() {
        if (singleton == null) {
            singleton = new Singleton();
        }
        return singleton;
    }
}

上面这种方法不推荐,原因是synchronized关键字会阻塞多线程。

5、推荐单例

这里提一下类的初始化,以下几种情况会触发类的初始化。

  • 遇到new、getstatic、putstatic或invokestatic这4条字节码指令时,如果类没有进行过初始化,则需要先触发其初始化。生成这4条指令的最常见的Java代码场景是:使用new关键字实例化对象的时候**、读取或设置一个类的静态字段(被final修饰、已在编译期把结果放入常量池的静态字段除外)的时候、**以及调用一个类的静态方法的时候。
  • 使用java.lang.reflect包的方法对类进行反射调用的时候,如果类没有进行初始化,则需要先触发其初始化。
  • 当初始化一个类的时候,如果发现其父类还没有进行过初始化,则需要先触发其父类的初始化。
  • 当虚拟机启动时,用户需要指定一个要执行的主类(包含main方法),虚拟机会先初始化这个主类。
/**
 * 按需加载,线程安全
 */
static class Singleton2 {
    /**
     * 外部类初始化不会触发内部类初始化
     */
    private static class Singleton3 {
        private static Singleton2 singleton2 = new Singleton2();
    }
    private Singleton2(){}
    /**
     * 第一次初始化Singleton3类时,会有JVM锁机制保证类只初始化一次,则singleton2只会被初始化一次
     */
    public Singleton2 getInstance() {
        return Singleton3.singleton2;
    }
}

第二种是使用枚举类,如下

/**
 * 还有一种就是Effective Java作者Josh Bloch提倡的写法,枚举单例,即时加载,线程安全
 * 其实就是之前提到过的 饿汉加载单例 的变形
 */
enum Singleton4 {
    SINGLETON_4;
    public void doSomething() {}
}

最后提一下双重检查单例,如下

/**
 * 其实众所周知的双重检查机制的单例还是存在错误的,但可以通过一些弥补措施来补救(不推荐)
 */
static class Singleton5 {
    /** 
     * 使用volatile关键字来禁止对singleton5的操作重排序,使volatile写操作对后续线程可见 
     */
    private volatile static Singleton5 singleton5;
    private Singleton5(){}
    public static Singleton5 getInstance() {
        if (singleton5 == null) {
            synchronized (Singleton5.class) {
                if (singleton5 == null) {
                    // 由于这一步存在指令重排序,因此线程不安全,可以通过volatile关键字来禁止重排序
                    singleton5 = new Singleton5();
                }
            }
        }
        return singleton5;
    }
}
总访问次数: 257次, 一般般帅 创建于 2018-03-16, 最后更新于 2020-06-25

进大厂! 欢迎关注微信公众号,第一时间掌握最新动态!