設(shè)計(jì)模式之裝飾器模式

0x01 前言


在編碼的時(shí)候,我們?yōu)榱藬U(kuò)展一個(gè)類常用的方法就是繼承,隨著擴(kuò)展功能越來(lái)越多,子類會(huì)變得越來(lái)越膨脹,
使系統(tǒng)變得不靈活。

裝飾器模式(Decorator pattern)允許像一個(gè)現(xiàn)有的對(duì)象添加新功能,同時(shí)又不改變其結(jié)構(gòu),他能讓我們?cè)跀U(kuò)展類的同時(shí),系統(tǒng)保持較好的靈活性。

0x02 廢話不多說(shuō),進(jìn)入正題,從一個(gè)場(chǎng)景開(kāi)始


假設(shè)我們?cè)诒本┯幸粔K地,在這塊地上我們要蓋有好幾間房間的別墅,每個(gè)房間的裝修費(fèi)用都不同,現(xiàn)在呢,我們需要對(duì)每個(gè)房間的費(fèi)用進(jìn)行計(jì)算并計(jì)算出別墅的價(jià)格。

//定義一個(gè)land表示這塊地
public interface Land {
     // 定義蓋別墅需要花錢的規(guī)則
    Integer cost();
}

那么land定義好了在這塊地上花錢的規(guī)則,但是蓋一間房需要多少錢呢?

此時(shí)我們定義一個(gè)Room類,這個(gè)類定義了一間房間需要建造的基本費(fèi)用。

public class Room implements Land{
    
    protected final int money = 1000 ;

    @Override
    public Integer cost() {
        return money ;
    }

}

開(kāi)始建造房間,房間建造了兩間,一間客廳(LivingRoom),一間餐廳(DiningRoom),建造完之后發(fā)現(xiàn)偌大房間連件家具都沒(méi)有,這時(shí)候,我們計(jì)算一間房成本的時(shí)候需要加上家具的價(jià)格,那么,上代碼...

public class LivingRoom extends Room{
    // 客廳
    @Override
    public Integer cost() {
        return this.land.cost()+200;
    }
}

public class DiningRoom extends Room{
    // 餐廳
    @Override
    public void cost() {
        return this.land.cost()+100;
    }
}

所以機(jī)智如你很容易就得到了建造一間客廳和一間餐廳的花費(fèi)..

public static void main(String[] args) {
        LivingRoom livingRoom = new LivingRoom();
        System.out.println("建造一間客廳需要花費(fèi)"+livingRoom.cost());
        DiningRoom diningRoom = new DiningRoom();
        System.out.println("建造一間餐廳需要花費(fèi)"+diningRoom.cost());
    }
// 得到結(jié)果
建造一間客廳需要花費(fèi)1200
建造一間餐廳需要花費(fèi)1100

那么,問(wèn)題來(lái)了:挖掘機(jī)...(啪,好好說(shuō)啊,挖掘機(jī)是什么鬼啊,喂)emmmmm,好接著說(shuō):

0x03 問(wèn)題的產(chǎn)生


這么做實(shí)現(xiàn)確實(shí)很容易,雖然我們很容易就得到了建造一間客廳和一間餐廳需要的花費(fèi),但是這樣的結(jié)構(gòu)不具備靈活性,如果....我是說(shuō)如果我的地比較小不能同時(shí)建造兩間房子,只能把客廳和餐廳建在同一個(gè)屋子里,那么該去怎么計(jì)算費(fèi)用?難道,還要很麻煩得去創(chuàng)建一個(gè)包含LivingDiningRoom類嗎?這樣做除了麻煩,而且還會(huì)使代碼重復(fù)。

既然提出了問(wèn)題,那么,肯定有解決的辦法,怎么解決呢?
欲知后事如何..(pia,知你妹啊,還來(lái)?),好吧,下面是解決辦法。

0x04 解決問(wèn)題


(敲黑板!!)重點(diǎn)來(lái)了,為了更好的解決問(wèn)題,我們需要做一些調(diào)整,同樣先聲明Land接口(抽象構(gòu)件,是具體構(gòu)件和抽象構(gòu)件的共同父類)和Room類( ConcreteComponent ,具體構(gòu)件,抽象構(gòu)件的子類,裝飾器可以額外添加職責(zé)。),不同的是引入房間的一個(gè)裝飾類RoomDecorator他實(shí)現(xiàn)了Land接口,因?yàn)闆](méi)有實(shí)現(xiàn)Land的cost方法,所以需要聲明為抽象類,并且定義一個(gè)Land接口為參數(shù)的構(gòu)造函數(shù),傳入對(duì)象會(huì)保存在land中,該屬性修飾符為protected方便子類訪問(wèn)。(劃重點(diǎn),考試要考)

public abstract class RoomDecorator implements Land{
    // 修飾符protected子類訪問(wèn)
    protected Land land ;
    
    public RoomDecorator(Land land) {  // 參數(shù)為實(shí)現(xiàn)Land接口的所有子類
        this.land = land ;
    }
}

我們重新定義客廳類和餐廳類

public class LivingRoom extends RoomDecorator{

    public LivingRoom(Land land) {
        super(land);
    }

    @Override
    public Integer cost() {
        return this.land.cost()+200;
    }
}

public class DiningRoom extends RoomDecorator{

    public DiningRoom(Land land) {
        super(land);
    }

    @Override
    public Integer cost() {
        return this.land.cost()+100;
    }
}

這兩個(gè)類都繼承了RoomDecorator,這意味著它們同時(shí)擁有指向Land接口的引用,當(dāng)它們的cost()方法被調(diào)用時(shí),都會(huì)先調(diào)用實(shí)現(xiàn)了Land接口的子類的cost()方法,然后執(zhí)行自己特有的操作。

所以這時(shí)候建造一間客廳和一間餐廳所需的費(fèi)用是這么計(jì)算的:

public static void main(String[] args) {
        LivingRoom livingRoom = new LivingRoom(new Room());
        System.out.println("建造一間客廳需要花費(fèi)"+livingRoom.cost());
        
        DiningRoom diningRoom = new DiningRoom(new Room());
        System.out.println("建造一間餐廳需要花費(fèi)"+diningRoom.cost());
    }
// 輸出
建造一間客廳需要花費(fèi)1200
建造一間餐廳需要花費(fèi)1100

接著回到剛才的問(wèn)題:建造一間有客廳又有餐廳的房間所需費(fèi)用,代碼如下

public static void main(String[] args) {
        LivingRoom diningLivingRoom = new LivingRoom(new DiningRoom(new Room()));
        System.out.println("建造一間客廳和餐廳需要花費(fèi)"+diningLivingRoom.cost());
    }
// 輸出
建造一間客廳和餐廳需要花費(fèi)1300

我們計(jì)算建造費(fèi)用的思路是:我們計(jì)算一間房的基礎(chǔ)費(fèi)用-->在基礎(chǔ)房間裝飾為客廳的費(fèi)用-->在客廳的基礎(chǔ)上加上裝飾餐廳的費(fèi)用-->得到包含客廳餐廳房間的費(fèi)用,已經(jīng)不需要通過(guò)麻煩的創(chuàng)建LivingDiningRoom類來(lái)計(jì)算包含餐廳客廳房間的建造費(fèi)用了。

這便是裝飾模式,通過(guò)一層層的裝飾得到我們可以靈活的得到想要的結(jié)果,可以輕松地添加組件或者裝飾類來(lái)創(chuàng)建靈活的結(jié)構(gòu)。

0x05 完整代碼

//定義一個(gè)land表示這塊地
//  Component:組件對(duì)象的接口,可以給這些對(duì)象動(dòng)態(tài)的添加職責(zé)
public interface Land {
     // 定義蓋別墅需要花錢的規(guī)則
    Integer cost();
}
// ConcreteComponent:具體的組件對(duì)象,實(shí)現(xiàn)了組件接口。該對(duì)象通常就是被裝飾器裝飾的原始對(duì)象,可以給這個(gè)對(duì)象添加職責(zé);
public class Room implements Land{
    protected final int money = 1000 ;
    @Override
    public Integer cost() {
        return money;
    }
}

// Decorator:所有裝飾器的父類,需要定義一個(gè)與組件接口一致的接口
public abstract class RoomDecorator implements Land{
    // 修飾符protected子類訪問(wèn)
    protected Land land ;
    public RoomDecorator(Land land) {
        this.land = land ;
    }
}

// 裝飾類具體構(gòu)件
public class LivingRoom extends RoomDecorator{
    public LivingRoom(Land land) {
        super(land);
    }
    @Override
    public Integer cost() {
        return this.land.cost()+200;
    }
}
// 裝飾類具體構(gòu)件
public class DiningRoom extends RoomDecorator{
    public DiningRoom(Land land) {
        super(land);
    }
    @Override
    public Integer cost() {
        return this.land.cost()+100;
    }
}

// 測(cè)試
public class RoomTest{
    public static void main(String[] args) {
        LivingRoom livingRoom = new LivingRoom(new Room());
        System.out.println("建造一間客廳需要花費(fèi)"+livingRoom.cost());
        
        DiningRoom diningRoom = new DiningRoom(new Room());
        System.out.println("建造一間餐廳需要花費(fèi)"+diningRoom.cost());
        
        LivingRoom diningLivingRoom = new LivingRoom(new DiningRoom(new Room()));
        System.out.println("建造一間客廳和餐廳需要花費(fèi)"+diningLivingRoom.cost());
    }
}

0x06 The End...


Tips:在北京有塊地一輩子都不可能的,如果可以,我愿意偷電動(dòng)車養(yǎng)你,點(diǎn)顆心在走嘛。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。