設計模式 #4 (裝飾器模式、介面卡模式)

凌丹妙耀發表於2020-09-19

設計模式 #4 (裝飾器模式、介面卡模式)


文章中所有工程程式碼和UML建模檔案都在我的這個GitHub的公開庫--->DesignPatternStar來一個好嗎?秋梨膏!


裝飾器模式

簡述:在不改變現有物件結構的情況下,為現有物件新增新功能。

需求:玩過那種女孩換裝那種遊戲嗎?什麼?沒玩過?猛男必玩的呀!現在需要選擇套裝進行裝扮,並計算當前服裝的花費。

反例 #1:

public abstract class Suit {
    private String name;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Suit(String name){
        this.name =name;
    }

    abstract int price();

    @Override
    public String toString() {
        return "Suit{" +
                "name='" + this.getName() + '\'' +  //注意這裡使用this.getName()
                "price='" + price() + '\'' +
                '}';
    }
}

public class Suit_01 extends Suit{
    public Suit_01() {
        super("猛男套裝");
    }

    @Override
    public int price() {
        return 1899;
    }
}
public class Suit_02 extends Suit{
    public Suit_02() {
        super("青春套裝");
    }

    @Override
    public int price() {
        return 2199;
    }
}
/*=====================客戶端=========================*/
public class negtive {
    public static void main(String[] args) {
        Person girl = new Person("Mary",new suit_02());
        System.out.println(girl);

        System.out.println("   ");

        Person boy = new Person("Jack",new suit_01());
        System.out.println(boy);
    }
}

這樣一看,面向抽象程式設計,分離的這麼好,這是一次不錯的設計。

UML類圖如下:

image-2020095242525418145112051

需求:現在遊戲出了新服飾,需要為原有的一個人物新增新的服飾進行裝扮,要求是不改變原有的裝扮。

學習七大設計原則都知道,開閉原則是一個大多數設計模式都遵守的設計原則,此需求要求不改變原有裝扮其實就是要求不改變原有物件--Suit的結構。

  • 如果單純地繼承每一個原有套裝類Suit,重寫相關方法進行類的增加,可能會造成程式碼的臃腫。

  • 長期以往隨著新增服飾增加,也將導致套裝類Suit的爆炸式增長。

此時熟悉換裝遊戲的朋友,不,熟悉設計模式的朋友們就知道要使用裝飾器模式進行設計了。

正例 #1:

//抽象服飾類
public abstract class Decorate extends Suit{
    protected Suit suit;

    public Decorate(Suit suit){
        super(suit.getName());
        this.suit = suit;
    }
}
//服飾——01
public class Canvas_Shoes extends Decorate{
    public Canvas_Shoes(Suit suit) {
        super(suit);
    }
    
    @Override
    public String getName() {
        return super.getName()+"   +帆布鞋";
    }
    @Override
    public int price() {
        return suit.price()+399;
    }
}
//服飾——02
public class Skirt extends Decorate {
    public Skirt(Suit suit) {
        super(suit);
    }

    @Override
    public String getName() {
        return suit.getName()+"  +短裙";
    }
    
    @Override
    public int price() {
        return suit.price()+499;
    }
}
/*=================客戶端====================*/
public class postive {
    public static void main(String[] args) {
        Suit_02 girl_suit = new Suit_02();
        Skirt skirt = new Skirt(girl_suit);
        Canvas_Shoes canvas_shoes = new Canvas_Shoes(skirt);

        System.out.println(canvas_shoes);
    }
}

UML類圖如下:

image-20200918204104962

這樣一來,增加套裝類,服飾類都不會違反開閉原則了。雖然還是會產生很多類。

但是客戶端需要使用的時候只需要用新增的裝飾器類(繼承重寫Decorate)巢狀使用進行裝飾套裝類(繼承重寫Suit)即可。簡單點說就是增加新一層外包裝--套娃,哈哈。

Java中,java.io就是用了裝飾器模式進行API設計:

image-20200913655892

image-20200918183831427

甚至我們還可以自己寫一個加到java.io中:

public class MyBufferReader extends Reader {
    private Reader in;

    public MyBufferReader(Reader in){
        this.in = in;
    }

    public String readLine() throws IOException {
        StringBuilder sb = new StringBuilder();
        int read;

        while(true){
            read = in.read();
            if (read == '\r')
                continue;
            if (read == '\n' || read == -1){
                break;
            }
            sb.append((char)read);
        }
        if (sb.toString().length() == 0){
            if (read == '\n'){
                return "";
            }else {
                return null;
            }
        }else {
            return sb.toString();
        }
    }

    @Override
    public int read(char[] cbuf, int off, int len) throws IOException {
        return 0;
    }

    @Override
    public void close() throws IOException {
        in.close();
    }

    /*===================客戶端===========================*/
    public static void main(String[] args) throws IOException {
        Reader in = new FileReader("E:\\1.txt");
        MyBufferReader myBufferReader = new MyBufferReader(in);
      //  BufferedReader br = new BufferedReader(myBufferReader);   可以層層進行套娃包裝

        String line;
        while(( line = myBufferReader.readLine() ) != null){
            System.out.println(line);
        }
    }
}

image-20200918192559454

輸出我近期投訴學校店大欺客的移動寬頻寫的小作文。

image-20200918192730552

在檢視抽象類Reader時。可以看到我們自己寫的MyBufferReader已經融入到裝飾類的群中,可以進行包裝其他Reader的子類,也可以被其他Reader子類包裝使用。

介面卡模式

簡述:介面卡模式(Adapter),將一個類的介面轉換成客戶希望的另外一個介面。Adapter模式使得原本由於介面不相容而不能一起工作的那些類可以一起工作。

這次不叫反例,叫前提條件。

現在中國球員去NBA發展,可是初來乍到,會有語言不同的情況,現有設計類如下:

public interface Foreign_Player {
    void Speak_Foreign_Language();
     String getName();
}

public class CHN_player implements Foreign_Player {
    private String name;

    public CHN_player(String name) {
        this.name =name;
    }

    public String getName() {
        return name;
    }

    @Override
    public void Speak_Foreign_Language() {
        System.out.println("用中文說戰術。。。。");
    }
}
public interface Native_Player {
    void speak_English();
    String getName();
}

public class NBA_player implements Native_Player {
    private String name;

    public NBA_player(String name) {
        this.name =name;
    }

    public String getName() {
        return name;
    }

    @Override
    public void speak_English() {
        System.out.println(name+"Talk about  Tactics in English。。。。");
    }
}

現在需要讓所有球員用英文傳達戰術,中國球員現學肯定來不及了(不能現改中國球員Foreign_Player介面違反開閉原則),那就需要一個翻譯人員,介面卡模式就派上用場了。

public class Adapter_Translator implements Native_Player {

    private Foreign_Player foreign_player;


    public Adapter_Translator(Foreign_Player foreign_player) {
        this.foreign_player = foreign_player;
    }

    public String getName() {
        return foreign_player.getName()+" 的翻譯人員";
    }

    @Override
    public void speak_English() {
        System.out.println("翻譯人員翻譯中文戰術成英語給隊友-------");
    }
}
/*=================客戶端====================*/
public class postive {
    public static void main(String[] args) {
        CHN_player yaoming = new CHN_player("yaoming");
        System.out.println(yaoming.getName());
        yaoming.Speak_Foreign_Language();

        //進行適配
        Native_Player translator =  new Adapter_Translator(yaoming);
        System.out.println(translator.getName());
        translator.speak_English();
    }
}

UML類圖如下:

image-202009191sss62235302

因為有了翻譯人員,中國球員不會因為語言不通的原因導致球員之間無法溝通的問題,這時候,中國球員就好像一個本土球員一樣在賽場上打球。

也就是兩個實現不同介面的、不存在聯絡的類,在不修改原始碼的前提下,通過介面卡,可以實現互通互用,遵守開閉原則

簡單總結一下介面卡的編寫套路:

  • 繼承需要適配成的抽象類,或者實現需要適配的介面。(例子中需要適配成Native_Player,介面卡Adapter_Translator就實現Native_Player

  • 組合原來不適配的抽象類或者介面。(例子中,介面卡Adapter_Translator組合了Foreign_Player介面)

得此套路,大功即成。

相關文章