1. 程式人生 > >《設計模式》之一文帶你理解策略模式、原型模式(深淺拷貝)、觀察者模式、裝飾模式

《設計模式》之一文帶你理解策略模式、原型模式(深淺拷貝)、觀察者模式、裝飾模式

原型模式

什麼是原型模式

原型模式是一個建立型的模式。原型二字表明瞭該模式應該有一個樣板例項,使用者從這個樣板物件中複製一個內部屬性一致的物件,這個過程也就是我們稱的“克隆”。被複制的例項就是我們所稱的“原型”,這個原型是可定製的。原型模式多用於建立複雜的或者構造耗時的例項,因為這種情況下,複製一個已經存在的例項可使程式執行更高效。(這裡先用比較高深的術語說明,下面會有圖解和白話方式表達)

克隆

原型模式的應用場景

1.類初始化需要消化非常多的資源,這個資源包括資料、硬體資源等,通過原型拷貝避免這些消耗。

2.通過new產生的一個物件需要非常繁瑣的資料準備或者許可權,這時可以使用原型模式。

3.一個物件需要提供給其他物件訪問,而且各個呼叫者可能都需要修改其值時,可以考慮使用原型模式拷貝多個物件供呼叫者使用,即保護性拷貝。

4.Spring框架中的多例就是使用原型。

深拷貝和淺拷貝

建立Book類

public class Book implements Cloneable {

    //名稱
    private String title;
    //圖片
    private ArrayList<String> listImg = new ArrayList<String>();
    //頁數
    private int pageNum;

    public
String getTitle() { return title; } public void setTitle(String title) { this.title = title; } public List<String> getListImg() { return listImg; } public void setListImg(ArrayList<String> listImg) { this.listImg = listImg; }
public int getPageNum() { return pageNum; } public void setPageNum(int pageNum) { this.pageNum = pageNum; } public void addImg(String imgName) { listImg.add(imgName); } public void showBook() { System.out.println("-------------start---------------"); System.out.println("title:"+title); for (String imgs : listImg) { System.out.println("img name:"+imgs); } System.out.println("pageNum:"+pageNum); System.out.println("-------------end-----------------"); } @Override protected Object clone() throws CloneNotSupportedException { super.clone(); } }

引用拷貝

Book book1 = new Book();
//設定屬性
book1.setTitle("圖書1");
book1.addImg("圖片1");
book1.setPageNum(100);
//輸出book1屬性
book1.showBook();
//引用拷貝
Book book2 = book1;
//book2設定屬性
book2.setTitle("圖書2");
book2.addImg("圖片2");
book2.setPageNum(200);
//輸出book2屬性
book2.showBook();
//輸出book1屬性
book1.showBook();
//列印地址
System.out.println(book1);
System.out.println(book2);
-------------start---------------
title:圖書1
img name:圖片1
pageNum:100
-------------end-----------------
-------------start---------------
title:圖書2
img name:圖片1
img name:圖片2
pageNum:200
-------------end-----------------
-------------start---------------
title:圖書2
img name:圖片1
img name:圖片2
pageNum:200
-------------end-----------------
com.tihom.prototype.Book@14ae5a5
com.tihom.prototype.Book@14ae5a5

在這裡插入圖片描述

從上圖就可以看出來,引用拷貝後引用的還是同一個堆區建立的物件,如果book2修改屬性的話,book1對應的屬性也會被改變。

淺拷貝

被複制物件的所有變數都含有與原來的物件相同的值,而所有的對其他物件的引用仍然指向原來的物件。即物件的淺拷貝會對“主”物件進行拷貝,但不會複製主物件裡面的物件。”裡面的物件“會在原來的物件和它的副本之間共享。

簡而言之,淺拷貝只會拷貝基本型別,不會拷貝引用型別。

修改Book類中clone方法的實現

@Override
protected Object clone() throws CloneNotSupportedException {
	//預設淺克隆
    Book book = (Book) super.clone();
    return book;
}
Book book1 = new Book();
//設定屬性
book1.setTitle("圖書1");
book1.addImg("圖片1");
book1.setPageNum(100);
//輸出book1屬性
book1.showBook();
//淺拷貝
Book book2 = (Book) book1.clone();
//book2設定屬性
book2.setTitle("圖書2");
book2.addImg("圖片2");
book2.setPageNum(200);
//輸出book2屬性
book2.showBook();
//輸出book1屬性
book1.showBook();
//列印地址
System.out.println(book1);
System.out.println(book2);
-------------start---------------
title:圖書1
img name:圖片1
pageNum:100
-------------end-----------------
-------------start---------------
title:圖書2
img name:圖片1
img name:圖片2
pageNum:200
-------------end-----------------
-------------start---------------
title:圖書1
img name:圖片1
img name:圖片2
pageNum:100
-------------end-----------------
com.tihom.prototype.Book@14ae5a5
com.tihom.prototype.Book@7f31245a

在這裡插入圖片描述 在這裡插入圖片描述

分析:

ArrayList因為是引用型別,所以不會被拷貝,book1的arrayList和book2的arrayList依然引用的是同一個堆區的ArrayList物件,那麼book2時的修改對book1同樣產生影響;String是比較特殊的情況,因為String被final修飾,所以本身就不能被修改,一開始我們的理解它不是基本型別肯定是是不會被拷貝的,那麼圖書1的值應該是要變的,但是因為final特性,它不能被改變,因此出現了這樣的輸出;int是基本型別,所以它會被拷貝,那麼圖書2的值就與圖書1的值無關,互不影響。

深拷貝

深拷貝是一個整個獨立的物件拷貝,深拷貝會拷貝所有的屬性,並拷貝屬性指向的動態分配的記憶體。當物件和它所引用的物件一起拷貝時即發生深拷貝。深拷貝相比於淺拷貝速度較慢並且花銷較大。

簡而言之,深拷貝把要複製的物件所引用的物件都複製了一遍,會在計算機中開闢一個新的記憶體地址。

修改clone的方法實現

@Override
protected Object clone() throws CloneNotSupportedException {
    //本來是淺拷貝,改為深拷貝
    Book book = (Book) super.clone();
    //將屬性深拷貝
    book.listImg = (ArrayList<String>) this.listImg.clone();
    return book;
}

main方法不需要修改

-------------start---------------
title:圖書1
img name:圖片1
pageNum:100
-------------end-----------------
-------------start---------------
title:圖書2
img name:圖片1
img name:圖片2
pageNum:200
-------------end-----------------
-------------start---------------
title:圖書1
img name:圖片1
pageNum:100
-------------end-----------------
com.tihom.prototype.Book@14ae5a5
com.tihom.prototype.Book@7f31245a

在這裡插入圖片描述 在這裡插入圖片描述

如圖,印證了我上面闡述的觀點

利用序列化實現深拷貝

// 序列化
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);

oos.writeObject(this);

// 反序列化
ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bis);

return ois.readObject();

單例裡面用原型的話採用深拷貝,淺拷貝會產生執行緒安全問題

策略模式

什麼是策略模式

定義了一系列的演算法,並將每一個演算法封裝起來,而且使它們還可以相互替換。策略模式讓演算法獨立於使用它的客戶而獨立變化。

策略模式演示

/**
 * 策略模式定義抽象公共演算法
 * @author TiHom
 * create at 2018/11/12 0012.
 */
abstract  class Strategy {

    public abstract void algorithmInterface();

}
/**
 * 初級會員 針對A演算法
 * @author TiHom
 * create at 2018/11/12 0012.
 */
public class StrategyA extends Strategy{
    @Override
    public void algorithmInterface() {
        System.out.println("初級會員針對A演算法");
    }
}

/**
 * 中級會員 針對B演算法
 * @author TiHom
 * create at 2018/11/12 0012.
 */
public class StrategyB extends Strategy{
    @Override
    public void algorithmInterface() {
        System.out.println("中級會員針對B演算法");
    }
}

/**
 * 高階會員 針對C演算法
 * @author TiHom
 * create at 2018/11/12 0012.
 */
public class StrategyC extends Strategy{
    @Override
    public void algorithmInterface() {
        System.out.println("高階會員針對C演算法");
    }
}
/**
 * 這裡是上下文,對策略的二次封裝
 * @author TiHom
 * create at 2018/11/12 0012.
 */
public class Context {

    private Strategy strategy;

    public Context(Strategy strategy) {
        this.strategy = strategy;
    }

    public void algorithmInterface() {
        strategy.algorithmInterface();
    }
}
public class Context {

    private Strategy strategy;

    public Context(Strategy strategy) {
        this.strategy = strategy;
    }

    public void algorithmInterface() {
        strategy.algorithmInterface();
    }
}
public class StrategyTest {

    public static void main(String[] args) {
        Context context = null;

        Strategy strategyA = new StrategyA();
        context = new Context(strategyA);
        context.algorithmInterface();

        Strategy strategyB = new StrategyB();
        context = new Context(strategyB);
        context.algorithmInterface();

        Strategy strategyC = new StrategyC();
        context = new Context(strategyC);
        context.algorithmInterface();
    }
}

Q:為什麼不直接呼叫而要通過一層context來包裝呢?

因為context是對策略進行二次封裝,目的是避免高層模組對策略的直接呼叫,且可讀性和擴充套件性提高.

Q:為什麼使用抽象類而不使用介面?

抽象類最大的好處是能夠實現繼承,子父重寫,且可以有普通方法的實現,而介面只能有方法,實現需要實現類去實現;且策略和模板方法、外觀模式可以一起使用;

策略模式的應用場景

策略模式的用意是針對一組演算法或邏輯,將每一個演算法或邏輯封裝到具有共同介面的獨立的類中,從而使得它們之間可以相互替換。

策略模式使得演算法或邏輯可以在不影響到客戶端的情況下發生變化。說到策略模式就不得不提及OCP(Open Closed Principle) 開閉原則,即對擴充套件開放,對修改關閉。策略模式的出現很好地詮釋了開閉原則,有效地減少了分支語句。

觀察者模式

對於觀察者模式我就不詳細說明了,因為使用場景也挺多的,專案中開發經常使用到的MQ就是觀察者模式的應用

什麼是觀察者模式

觀察者模式(Observer),是一種行為性模型,行為型模式關注的是系統中物件之間的相互互動,解決系統在執行時物件之間的相互通訊和協作,進一步明確物件的職責。相比來說,建立型模式關注物件的建立過程,結構型模式關注物件和類的組合關係。

觀察者模式的應用場景

觀察者模式主要用於1對N的通知。當一個物件的狀態變化時,他需要及時告知一系列物件,令他們做出相應。

實現有兩種方式:

推:每次都會把通知以廣播的方式傳送給所有觀察者,所有的觀察者只能被動接收。

拉:觀察者只要知道有情況即可,至於什麼時候獲取內容,獲取什麼內容,都可以自主決定。

裝飾模式

什麼是裝飾模式

裝飾器模式,也成為包裝模式,顧名思義,就是對已經存在的某些類進行裝飾,以此來擴充套件一些功能。其結構圖如下: 在這裡插入圖片描述

Component為統一介面,也是裝飾類和被裝飾類的基本型別。

ConcreteComponent為具體實現類,也是被裝飾類,他本身是個具有一些功能的完整的類。

Decorator是裝飾類,實現了Component介面的同時還在內部維護了一個ConcreteComponent的例項,並可以通過建構函式初始化。而Decorator本身,通常採用預設實現,他的存在僅僅是一個宣告:我要生產出一些用於裝飾的子類了。而其子類才是賦有具體裝飾效果的裝飾產品類。

ConcreteDecorator是具體的裝飾產品類,每一種裝飾產品都具有特定的裝飾效果。可以通過構造器宣告裝飾哪種型別的ConcreteComponent,從而對其進行裝飾。

結構圖

在這裡插入圖片描述

裝飾與代理區別

裝飾器模式關注於在一個物件上動態的新增方法,然而代理模式關注於控制對物件的訪問。換句話說,用代理模式,代理類(proxy class)可以對它的客戶隱藏一個物件的具體資訊。因此,當使用代理模式的時候,我們常常在一個代理類中建立一個物件的例項。並且,當我們使用裝飾器模式的時候,我們通常的做法是將原始物件作為一個引數傳給裝飾者的構造器。

裝飾模式應用場景

在IO中,具體構件角色是節點流,裝飾角色是過濾流。

FilterInputStream和FilterOutputStream是裝飾角色,而其他派生自它們的類則是具體裝飾角色。

DataOutputStream out=new DataOutputStream(new FileOutputStream());

這就是裝飾者模式,DataOutputStream是裝飾者子類,FileOutputStream是實現介面的子類。

這裡不會呼叫到裝飾者類–FilterOutputStream,只是作為繼承的另一種方案,對客戶端來說是透明的,是為了功能的擴張.