代碼碼雲倉庫地址:https://gitee.com/dzxmy/design_pattern
常用的創建型設計模式有:工廠方法模式,抽象工廠模式,建造者模式,單例模式。
不常用的創建型設計模式有:簡單工廠,原型模式
一、簡單工廠
定義:由一個工廠對象決定創建出哪一種產品類的實例
類型:創建型,但不屬於GOF23種設計模式
適用場景:
- 工廠類負責創建的對象比較少
- 客戶端應用層只知道傳入工廠類的參數對於如何創建對象不關心
優點:只需要傳入一個正確的參數,就可以獲取你所需要的對象,而無須知道其創建細節
缺點:工廠類的職責相對過重,增加新的產品,需要修改工廠類的判斷邏輯,違背開閉原則
com.dzx.design.creational.simplefactory 包下代碼: 簡單工廠
二、工廠方法
定義:定義一個創建對象的接口,但讓實現這個接口的類來決定實例化哪個類,工廠方法讓類的實例化推遲到子類中進行
類型:創建型
適用場景:創建對象需要大量重複的代碼,客戶端應用層不依賴於產品類實例如何被創建、實現等細節,一個類通過其子類來指定創建哪個對象
優點:用戶只需要關心所需產品對應的工廠,無須關心創建細節,加入新產品符合開閉原則,提高可擴展性
缺點:類的個數容易過多,增加複雜度,增加了系統的抽象性和理解難度
com.dzx.design.creational.factorymethod 包下代碼:工廠方法
三、抽象工廠
定義:抽象工廠模式提供一個創建一系列相關或相互依賴對象的接口
無須指定它們具體的類
類型:創建型
適用場景:
- 客戶端應用層不依賴於產品類實例如何被創建、實現等細節
- 強調一系列相關的產品對象(屬於同一產品族)一起使用創建對象需要大量重複的代碼
- 提供一個產品類的庫,所有的產品以同樣的接口出現,從而使客戶端不依賴於具體實現
優點:具體產品在應用層代碼隔離,無需關心創建細節,將一個系列的產品族統一到一起創建
缺點:規定了所有可能被創建的產品集合,產品族中擴展新的產品困難,需要修改抽象工廠的接口
增加了系統的抽象性和理解難度
com.dzx.design.creational.abstractfactory 包下代碼:抽象工廠
四、建造者模式
定義:將一個複雜對象的構建與它的表示分離,使得同樣的構建過程可以創建不同的表示
用戶只需要指定建造的類型就可以得到它們,建造過程及細節不需要知道
類型:創建型
適用場景:
- 如果一個對象有非常複雜的內部結構(很多屬性)
- 想把複雜對象的創建和使用分離
優點:封裝性好,創建和使用分離,擴展性好、建造類之間一定程度上解耦
缺點:產生多餘的builder對象,產品內部發生變化,建造者都要修改,成本較大
com.dzx.design.creational.builder 包下代碼: 建造者模式
建造者模式改進版本(更加常用)
com.dzx.design.creational.builder.v2 包下代碼: 建造者模式改進版本
五、單例模式
com.dzx.design.creational.singletonexample 包下代碼:單例模式
單例模式的 幾種方式比較
懶漢模式
/**
* 懶漢模式
* 單例的實例在第一次使用的時候進行創建
* <p>
* 在單線程的環境下,沒有問題,但是在多線程的環境下就會出問題。
*/
@NotThreadSafe
@Slf4j
public class SingletonExample1 {
//私有構造函數
private SingletonExample1() {
}
//單例對象
private static SingletonExample1 instance = null;
//靜態的工廠方法
//懶漢模式本身時線程不安全的,加上了synchronized關鍵字之後就可以實現安全
//但是降低了性能,因此並不推薦這種寫法
public static synchronized SingletonExample1 getInstance() {
if (instance == null) {
instance = new SingletonExample1();
}
return instance;
}
}
餓漢模式
/**
* 餓漢模式
*/
public class SingletonExample2 {
private SingletonExample2() {
}
private static SingletonExample2 singletonExample2 = new SingletonExample2();
public static SingletonExample2 getInstance() {
return singletonExample2;
}
}
懶漢模式+ 雙重檢測同步鎖
/**
* 懶漢模式 -》》雙重同步鎖單例模式
*/
@NotThreadSafe
@Slf4j
public class SingletonExample3 {
//私有構造函數
private SingletonExample3() {
}
/**
* 1.分配對象的內存空間
* 2.初始化對象
* 3.設置instance 指向剛分配的內存
* 在單線程的情況下沒有問題,但是在多線程的情況下
*
* jvm 和cpu 優化,發生指令重排
*
* 1.分配對象的內存空間
* 2.設置instance 指向剛分配的內存
* 3.初始化對象
*
* 因此我們需要限制SingletonExample3類的創建的時候的指令重排,添加volatile關鍵字
*/
//單例對象
//volatile 加上 雙重檢測機制,禁止指令重排
private volatile static SingletonExample3 instance = null;
//靜態的工廠方法
public static SingletonExample3 getInstance() {
if (instance == null) { //雙重檢測機制
synchronized (SingletonExample3.class) { //同步鎖
if (instance == null) {
instance = new SingletonExample3();
}
}
}
return instance;
}
}
餓漢模式 (靜態塊的方式)
/**
* static 靜態塊的餓漢模式
*/
public class SingletonExample6 {
private SingletonExample6(){}
private static SingletonExample6 instance =null;
static {
instance = new SingletonExample6();
}
private static SingletonExample6 getInstance(){
return instance;
}
public static void main(String[] args){
System.out.println(getInstance());
System.out.println(getInstance());
}
}
枚舉模式(最推薦)
/**
* 枚舉模式,最安全
* 利用枚舉的特性,一個枚舉的構造方法只會被調用一次實現單例模式,推薦使用這種方式
* <p>
* 優點:1、相比懶漢模式,更能保證線程安全性
* 2、相比餓漢模式,也只會在第一次使用的時候進行創建實例,不會造成資源浪費
*/
@ThreadSafe
@Recommend
public class SingletonExample7 {
private SingletonExample7() {
}
public static SingletonExample7 getInstance() {
return Singleton.INSTANCE.getInstance();
}
private enum Singleton {
INSTANCE; //只有一個枚舉變量,保證構造方法只調用一次
private SingletonExample7 singletonExample7;
//jvm保證這個方法絕對只調用一次
Singleton() {
System.out.println("我只會被調用一次");
singletonExample7 = new SingletonExample7();
}
public SingletonExample7 getInstance() {
return singletonExample7;
}
}
public static void main(String[] args) {
System.out.println(SingletonExample7.getInstance());
System.out.println(SingletonExample7.getInstance());
System.out.println(SingletonExample7.getInstance());
}
}
六、原型模式
定義:指原型實例指定創建對象的種類,並且通過拷貝這些原型創建新的對象,不需要知道任何創建的細節,不調用構造函數
類型:創建型
適用場景:
- 類初始化消耗較多資源
- new產生的一個對象需要非常繁瑣的過程(數據準備,訪問權限等)
- 構造函數比較複雜
- 循環體中生產大量對象時
優點:
- 原型模式性能比直接new一個對象性能高
- 簡化創建過程
缺點:
- 必須配備克隆方法
- 對克隆複雜對象或者對克隆出的對象進行復雜改造時,容易引入風險
- 深拷貝,淺拷貝要運用得當
深克隆:
一個類實現cloneable接口,並在重寫clone方法的時候對該類的引用數據類型對象屬性也需要調用clone方法進行拷貝
淺克隆:
一個類實現cloneable接口,默認就是淺克隆
com.dzx.design.creational.prototype 包下代碼 原型模式
package com.dzx.design.creational.prototype.clone;
import java.util.Date;
/**
* @author dzx
* @ClassName:
* @Description: 原型模式
* @date 2019年07月30日 10:58:53
*/
public class Pig implements Cloneable {
private String name;
private Date birthday;
@Override
public String toString() {
return "Pig{" +
"name='" + name + '\'' +
", birthday=" + birthday +
'}' + super.toString();
}
public Pig(String name, Date birthday) {
this.name = name;
this.birthday = birthday;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Date getBirthday() {
return birthday;
}
public void setBirthday(Date birthday) {
this.birthday = birthday;
}
@Override
protected Object clone() throws CloneNotSupportedException {
Pig pig = (Pig) super.clone();
pig.birthday = (Date) pig.getBirthday().clone();
return pig;
}
}
package com.dzx.design.creational.prototype.clone;
/**
* @author dzx
* @ClassName:
* @Description: 原型模式
* @date 2019年07月15日 10:02:06
*/
import java.io.Serializable;
/**
* 餓漢式+靜態代碼塊
*/
public class SingletonExample4 implements Serializable,Cloneable {
private SingletonExample4() {
}
/**
* 防止通過反射和克隆方法破壞單例模式
* @return
* @throws CloneNotSupportedException
*/
@Override
protected Object clone() throws CloneNotSupportedException {
return getInstance();
}
private static SingletonExample4 singletonExample4 = null;
static {
singletonExample4 = new SingletonExample4();
}
public static SingletonExample4 getInstance() {
return singletonExample4;
}
}
克隆測試以及 通過克隆破壞單例模式
package com.dzx.design.creational.prototype.clone;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Date;
/**
* @author dzx
* @ClassName:
* @Description: 原型模式
* @date 2019年07月30日 10:59:57
*/
public class Test {
public static void main(String[] args) throws CloneNotSupportedException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {
Date date = new Date(0);
Pig pig = new Pig("佩奇", date);
Pig clone = (Pig) pig.clone();
System.out.println(pig);
System.out.println(clone);
//默認爲淺克隆,當我們修改pig對象的生日時候,clone對象的生日也跟着被修改了
//如果要做到深克隆,就需要重寫深克隆,並且在克隆的時候,需要克隆pig類裏面屬性爲引用數據類型的對象
//否則很容易引起bug
pig.getBirthday().setTime(66666666L);
System.out.println(pig);
System.out.println(clone);
//利用反射和克隆破壞單例模式
SingletonExample4 instance = SingletonExample4.getInstance();
Method clone1 = instance.getClass().getDeclaredMethod("clone");
clone1.setAccessible(true);//原本是protected,需要打開訪問權限
//執行clone方法
SingletonExample4 invoke = (SingletonExample4) clone1.invoke(instance);
//發現instance 和 invoke 兩個對象的引用地址是不同的,說明單例已被破壞掉
//解決防範:1 不實現cloneable接口 2在重寫的clone()方法中調用getInstance()方法
System.out.println(instance);
System.out.println(invoke);
}
}