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
欢迎关注微信公众号,第一时间掌握最新动态!