1. 程式人生 > >《設計模式》之一文帶你理解建造者模式、模板方法、介面卡模式、外觀模式

《設計模式》之一文帶你理解建造者模式、模板方法、介面卡模式、外觀模式

我的github,到時上傳例子程式碼
https://github.com/tihomcode


《設計模式》之一文帶你理解單例、JDK動態代理、CGLIB動態代理、靜態代理


建造者模式

什麼是建造者模式

建造者模式:是將一個複雜的物件的構建與它的表示分離,使得同樣的構建過程可以建立不同的表示。

工廠類模式提供的是建立單個類的模式,而建造者模式則是將各種產品集中起來進行管理,用來建立複合物件,所謂複合物件就是指某個類具有不同的屬性,其實建造者模式就是前面抽象工廠模式和最後的Test結合起來得到的。

建造者模式通常包括下面幾個角色

1、Builder:給出一個抽象介面,以規範產品物件的各個組成成分的建造。這個介面規定要實現複雜物件的哪些部分的建立,並不涉及具體的物件部件的建立。

2、ConcreteBuilder:實現Builder介面,針對不同的商業邏輯,具體化複雜物件的各部分的建立。 在建造過程完成後,提供產品的例項。

3、Director:呼叫具體建造者來建立複雜物件的各個部分,在指導者中不涉及具體產品的資訊,只負責保證物件各部分完整建立或按某種順序建立。

4、Product:要建立的複雜物件。

建造者模式演示

public interface PersonBuilder {

    void builderHead();

    void builderBody();

    void builderFoot();

    Person builderPerson
(); //組裝 }
public class Person {

    private String head;
    private String body;
    private String foot;

    public String getHead() {
        return head;
    }

    public void setHead(String head) {
        this.head = head;
    }

    public String getBody() {
        return body;
    }

    public
void setBody(String body) { this.body = body; } public String getFoot() { return foot; } public void setFoot(String foot) { this.foot = foot; } }
public class JPBuilder implements PersonBuilder {
    private Person person;

    public JPBuilder() {
        person = new Person();//建立一個Person例項,用於呼叫set方法
    }

    public void builderHead() {
        person.setHead("日本人黑眼睛 圓臉");
    }

    public void builderBody() {
        person.setBody("體格小");
    }

    public void builderFoot() {
        person.setFoot("腿短");
    }

    public Person builderPerson() {
        return person;
    }
}

public class USBuilder implements PersonBuilder {
    private Person person;

    public USBuilder() {
        person = new Person();//建立一個Person例項,用於呼叫set方法
    }

    public void builderHead() {
        person.setHead("美國藍眼睛 長臉 尖鼻");
    }

    public void builderBody() {
        person.setBody("體格大");
    }

    public void builderFoot() {
        person.setFoot("腿長");
    }

    public Person builderPerson() {
        return person;
    }

}
public class PersonDirector {
    //一定要按順序
    public Person constructPerson(PersonBuilder pb) {
        pb.builderHead();
        pb.builderBody();
        pb.builderFoot();
        return pb.builderPerson();
    }

}
public class BuilderTest {

    public static void main(String[] args) {
        PersonDirector personDirector = new PersonDirector();
        //建造美國人
        Person person = personDirector.constructPerson(new USBuilder());
        System.out.println(person.getHead());
        System.out.println(person.getBody());
        System.out.println(person.getFoot());
        //建造日本人
        person = personDirector.constructPerson(new JPBuilder());
        System.out.println(person.getHead());
        System.out.println(person.getBody());
        System.out.println(person.getFoot());
    }
}

建造者模式應用場景

1.需要生成的產品物件有複雜的內部結構,這些產品物件通常包含多個成員屬性。

2.需要生成的產品物件的屬性相互依賴,需要指定其生成順序。

3.物件的建立過程獨立於建立該物件的類。在建造者模式中引入了指揮者類,將建立過程封裝在指揮者類中,而不在建造者類中。

4.隔離複雜物件的建立和使用,並使得相同的建立過程可以建立不同的產品。

5.Java中的StringBuilder,底層是陣列(存單個字元)將字元整合在一起變成字串的過程

建造者模式與工廠模式的區別

建造者模式更加關注與零件裝配的順序

工廠模式是將物件的全部建立過程封裝在工廠類中,由工廠類向客戶端提供最終的產品;而建造者模式中,建造者類一般只提供產品類中各個元件的建造,而將具體建造過程交付給導演類。由導演類負責將各個元件按照特定的規則組建為產品,然後將組建好的產品交付給客戶端。簡單點說,建造者模式主要負責的是部位、零件的細節實現,而裝配過程讓Director類來完成,但是工廠的話就是細節和裝配都使用工廠類完成

建造者模式與工廠模式類似,適用的場景也很相似。一般來說,如果產品的建造很複雜,那麼請用工廠模式;如果產品的建造更復雜,那麼請用建造者模式。

建造者模式的優缺點

優點

建造者模式的封裝性很好。使用建造者模式可以有效的封裝變化,在使用建造者模式的場景中,一般產品類和建造者類是比較穩定的,因此,將主要的業務邏輯封裝在導演類中對整體而言可以取得比較好的穩定性。

在建造者模式中,客戶端不必知道產品內部組成的細節,將產品本身與產品的建立過程解耦,使得相同的建立過程可以建立不同的產品物件。

可以更加精細地控制產品的建立過程 。將複雜產品的建立步驟分解在不同的方法中,使得建立過程更加清晰,也更方便使用程式來控制建立過程。

其次,建造者模式很容易進行擴充套件。如果有新的需求,通過實現一個新的建造者類就可以完成,基本上不用修改之前已經測試通過的程式碼,因此也就不會對原有功能引入風險。符合開閉原則。

缺點

建造者模式所建立的產品一般具有較多的共同點,其組成部分相似,如果產品之間的差異性很大,則不適合使用建造者模式,因此其使用範圍受到一定的限制。

如果產品的內部變化複雜,可能會導致需要定義很多具體建造者類來實現這種變化,導致系統變得很龐大。

模板方法

什麼是模板方法

模板方法模式:定義一個操作中的演算法骨架,而將一些步驟延遲到子類中。模板方法使得子類可以不改變一個演算法的結構即可重定義該演算法的某些特定步驟。

重複程式碼全部在父類裡面,不同業務的,抽取給子類進行實現。抽取過程—抽象方法。

核心:處理某個流程的程式碼已經都具備,但是其中某個節點的程式碼暫時不能確定。因此,我們採用工廠方法模式,將這個節點的程式碼實現轉移給子類完成。即:處理步驟在父類中定義好,具體的實現延遲到子類中定義。

說白了,就是將一些相同操作的程式碼,封裝成一個演算法的骨架。核心的部分留在子類中操作,在父類中只把那些骨架做好。

例如:

1.去銀行辦業務,銀行給我們提供了一個模板就是:先取號,排對,辦理業務(核心部分我們子類完成),給客服人員評分,完畢。 這裡辦理業務是屬於子類來完成的,其他的取號,排隊,評分則是一個模板。

2.去餐廳吃飯,餐廳給提供的一套模板就是:先點餐,等待,吃飯(核心部分我們子類完成),買單。這裡吃飯是屬於子類來完成的,其他的點餐,買單則是餐廳提供給我們客戶的一個模板。

3.簡訊介面對接,我們專案中要使用到簡訊介面,假設現在我們需要的流程有

日誌系統開始->傳送請求->返回結果->扣款->日誌系統結束

因為不同運營商他們的請求方式不一樣,返回的結果也不一樣,那麼未加粗部分就相當於一個模板,是固定的,我們只需要將加粗部分抽象給子類去實現不同運營商的介面。

模板方法演示

下面我們就以簡訊運營商這個例子來寫Demo

/**
 * 簡訊模板
 * @author TiHom
 * create at 2018/11/11 0011.
 */
public abstract class MsgTemplate {

    public void sendMsg() {
        //1.日誌
        addHeadLog();
        //2.呼叫不同的運營商傳送簡訊
        httpRequest();
        //3.結束日誌報文
        addFootLog();
    }

    public abstract void httpRequest();

    private void addHeadLog() {
        System.out.println("呼叫運營商開始記錄日誌。。。");
    }

    private void addFootLog() {
        System.out.println("呼叫運營商結束記錄日誌。。。");
    }

}
/**
 * 中國移動介面
 * @author TiHom
 * create at 2018/11/11 0011.
 */
public class CMCC extends MsgTemplate{

    @Override
    public void httpRequest() {
        System.out.println("http://cmcc.tihom.com");
    }
}

/**
 * 中國聯通介面
 * @author TiHom
 * create at 2018/11/11 0011.
 */
public class CUCC extends MsgTemplate{

    @Override
    public void httpRequest() {
        System.out.println("http://cucc.tihom.com");
    }
}

/**
 * 中國電信介面
 * @author TiHom
 * create at 2018/11/11 0011.
 */
public class CTCC extends MsgTemplate{

    @Override
    public void httpRequest() {
        System.out.println("http://ctcc.tihom.com");
    }
}
/**
 * @author TiHom
 * create at 2018/11/11 0011.
 */
public class ClientTemplate {

    public static void main(String[] args) {
        MsgTemplate cmcc = new CMCC();
        cmcc.sendMsg();
        MsgTemplate cucc = new CUCC();
        cucc.sendMsg();
        MsgTemplate ctcc = new CTCC();
        ctcc.sendMsg();
    }
}

注意交給子類處理時要用到抽象方法,在以後的設計模式中會經常見到抽象類和介面使用

模板方法應用場景

實現一些操作時,整體步驟很固定,但是呢。就是其中一小部分容易變,這時候可以使用模板方法模式,將容易變的部分抽象出來,供子類實現。

1.資料庫訪問的封裝

2.Junit單元測試

3.servlet中關於doGet/doPost方法的呼叫

4.Hibernate中模板程式

5.spring中JDBCTemplate

6.HibernateTemplate

介面卡

什麼是介面卡

在設計模式中,介面卡模式(英語:adapter pattern)有時候也稱包裝樣式或者包裝(wrapper)。將一個類的介面轉接成使用者所期待的。一個適配使得因介面不相容而不能在一起工作的類工作在一起,做法是將類自己的介面包裹在一個已存在的類中。

舉個例子,比如港版的蘋果和國行的蘋果,港版會提供三叉頭的充電頭,而國行提供的是雙插頭的充電頭,那麼我們想充電的話就需要另外去買一個轉接頭才能使用雙插頭充電

介面卡演示

我們就拿日本電飯煲的例子進行說明,日本電飯煲電源介面標準是110V電壓,而中國標準電壓介面是220V,所以要想在中國用日本電飯煲,需要一個電源轉換器。

public interface CN220VInterface {

    public void connect();

}

public class CN220VInterfaceImpl implements CN220VInterface {
    public void connect() {
        System.out.println("中國220V,接通電源,開始工作");
    }
}
public interface JP110VInterface {

    //電源介面
    public void connect();
}

public class JP110VInterfaceImpl implements JP110VInterface {
    public void connect() {
        System.out.println("日本110V,接通電源,開始工作..");
    }
}
public class ElectricCooker {

    private JP110VInterface jp110VInterface;//日本電飯煲

    //這裡必須傳日本110V的介面進來
    ElectricCooker(JP110VInterface jp110VInterface){
        this.jp110VInterface = jp110VInterface;
    }

    public void cook(){
        jp110VInterface.connect();
        System.out.println("開始做飯了..");
    }

}
public class PowerAdaptor implements JP110VInterface {

    private CN220VInterface cn220VInterface;

    public PowerAdaptor(CN220VInterface cn220VInterface) {
        this.cn220VInterface = cn220VInterface;
    }

    public void connect() {
        cn220VInterface.connect();
    }

}

public class AdaptorTest {

    public static void main(String[] args) {
        CN220VInterface cn220VInterface = new CN220VInterfaceImpl();
        PowerAdaptor powerAdaptor = new PowerAdaptor(cn220VInterface);
        //電飯煲
        ElectricCooker cooker = new ElectricCooker(powerAdaptor);
        cooker.cook();//使用了介面卡,在220V的環境可以工作了

    }
}

這裡很好理解,因為電飯煲本身就是日本介面的,所以它cook方法必須是110V日本電壓來做,而介面卡的作用就是將220V環境變成110V,因為它最終是要適合110V,所以要實現110V介面,但是它同時要連線220V,所以引入220V。

介面卡的分類

介面卡分為:類介面卡、物件適配、介面適配方式

類介面卡方式採用繼承方式,物件適配方式使用建構函式傳遞

介面卡應用場景

1.我們在使用第三方的類庫,或者說第三方的API的時候,我們通過介面卡轉換來滿足現有系統的使用需求。

2.我們的舊系統與新系統進行整合的時候,我們發現舊系統的資料無法滿足新系統的需求,那麼這個時候,我們可能需要介面卡,完成呼叫需求。

3.我們在使用不同資料庫之間進行資料同步。(我這裡只是分析的是通過程式來說實現的時候的情況。還有其他的很多種方式[資料庫同步])

4.springmvc介面卡,做請求攔截分發

5.OutputStreamWriter:是Writer的子類,將輸出的字元流變為位元組流,即:將一個字元流的輸出物件變為位元組流的輸出物件。

6.InputStreamReader:是Reader的子類,將輸入的位元組流變為字元流,即:將一個位元組流的輸入物件變為字元流的輸入物件。

外觀模式

什麼是外觀模式

外觀模式(Facade Pattern)門面模式,隱藏系統的複雜性,並向客戶端提供了一個客戶端可以訪問系統的介面。這種型別的設計模式屬於結構型模式,它向現有的系統新增一個介面,來隱藏系統的複雜性。

這種模式涉及到一個單一的類,該類提供了客戶端請求的簡化方法和對現有系統類方法的委託呼叫。

其實就是封裝。

外觀模式演示

使用者下單,需要呼叫阿里簡訊介面、郵件介面、微信推送介面

public interface EmailMsgService {

    public void sendMsg();
}

public class EmailMsgServiceImpl implements EmailMsgService {
    public void sendMsg() {
        System.out.println("傳送郵件");
    }
}
public interface SmsMsgService {

    public void sendMsg();
}

public class SmsMsgServiceImpl implements SmsMsgService {
    public void sendMsg() {
        System.out.println("簡訊推送");
    }
}
public interface WechatMsgService {

    public void sendMsg();
}

public class WechatMsgServiceImpl implements WechatMsgService {
    public void sendMsg() {
        System.out.println("微信推送");
    }
}
public class Computer {

    EmailMsgService emailMsgService;
    SmsMsgService smsMsgService;
    WechatMsgService wechatMsgService;

    public Computer() {
        emailMsgService = new EmailMsgServiceImpl();
        smsMsgService = new SmsMsgServiceImpl();
        wechatMsgService = new WechatMsgServiceImpl();
    }

    public void sendMsg() {
        emailMsgService.sendMsg();
        smsMsgService.sendMsg();
        wechatMsgService.sendMsg();
    }

}
public class FacadeTest {
    public static void main(String[] args) {
        /*下面這些就是沒有使用外觀模式時的呼叫 */
//        EmailMsgService emailMsgService = new EmailMsgServiceImpl();
//        SmsMsgService smsMsgService = new SmsMsgServiceImpl();
//        WechatMsgService wechatMsgService = new WechatMsgServiceImpl();
//        emailMsgService.sendMsg();
//        smsMsgService.sendMsg();
//        wechatMsgService.sendMsg();
        /* 這是使用了外觀模式後的用法,其實就是封裝 */
        Computer computer = new Computer();
        computer.sendMsg();
    }
}