iT邦幫忙

2019 iT 邦幫忙鐵人賽

DAY 7
0
自我挑戰組

初探設計模式系列 第 7

[ Day 7 ] 初探設計模式 - 裝飾模式(Decorator)

前言

假日第二天,昨天複習了一下這星期學得幾種模式,

接下來學習新的模式裝飾模式(Decorator Pattern)

在裝飾的順序內容會影響到表現(互相覆蓋內褲外穿?),

很適合使用裝飾模式。

  • 裝飾模式的角色有元件裝飾裝飾需要建構在被裝飾的元件上
  • 是一種繼承關係的替代方案
  • 裝飾物不同但是元件的本質不變。
定義:動態的給一個類別添加額外的職責。就增加功能來說,裝飾模式相比產生子類別更為靈活。

定義引用於「設計模式:Android原始碼解析與應用」 page.430

UML

類別圖提供在這裡
 Decorator Pattern

  • Component:被裝飾的核心元件
  • Decorator:裝飾核心的其他元件

實作

假設一個餐廳有各種套餐,主餐沙拉飲料甜點..等,

不同套餐的餐點不一樣,低消需要一杯飲料,

我們試著用裝飾模式實現的話...

有一間餐廳

public class Restaurant {

    private void minimumOrder(){
        System.out.println("一杯飲料");
    }

    //低消是一杯飲料
    public void order(){
        minimumOrder();
    }

}

商業午餐除了飲料還有沙拉跟主餐

public class BusinessLunch extends Restaurant {

    private Restaurant restaurant;

    public BusinessLunch(Restaurant restaurant){
        this.restaurant = restaurant;
    }

    private void salad(){
        System.out.println("一盤沙拉");
    }

    private void mainMeal(){
        System.out.println("一份主餐");
    }

    @Override
    public void order() {
        super.order();
        salad();
        mainMeal();
    }
}

簡餐多了湯品

public class SimpleCombo extends BusinessLunch {

    public SimpleCombo(Restaurant restaurant) {
        super(restaurant);
    }

    private void soup(){
        System.out.println("一份湯品");
    }

    @Override
    public void order() {
        super.order();
        soup();
    }
}

全餐再多了甜點

public class FullCombo extends SimpleCombo {

    public FullCombo(Restaurant restaurant) {
        super(restaurant);
    }

    private void sweet(){
        System.out.println("一份甜點");
    }

    @Override
    public void order() {
        super.order();
        sweet();
    }
}

測試一下

   public void test(){

        Restaurant restaurant = new Restaurant();

        SimpleCombo simpleCombo = new SimpleCombo(restaurant);

        System.out.println("簡餐:");
        simpleCombo.order();

        BusinessLunch businessLunch = new BusinessLunch(restaurant);
        System.out.println("商業午餐:");
        businessLunch.order();

        FullCombo fullCombo = new FullCombo(restaurant);
        System.out.println("全餐:");
        fullCombo.order();


    }

結果:

簡餐:
一杯飲料
一盤沙拉
一份主餐
一份湯品
商業午餐:
一杯飲料
一盤沙拉
一份主餐
全餐:
一杯飲料
一盤沙拉
一份主餐
一份湯品
一份甜點

實現出來後發覺有一點問題,問題點在於,Decorator Pattern其實是要為了減少或替代繼承的使用,因為如果套餐間有互相繼承的關係,會提高系統的耦合性。系統設計的原則需要透過某種方式封裝可能的變化,並且減少類別之間的互相影響(降低耦合度),如果未來新的套餐出了或是套餐的餐點改變了,只需要新增一個類別或是修改一個類別,可以很輕鬆的滿足需求。

所以接下來我們試著根據原則重新實現...。

訂單當成元件另外餐點當成修飾者

訂單類別和餐點基礎類別...

public class Order {
    public void show(){

    }
}

public class Item extends Order {
    protected Order order;

    public void decorate(Order order){
        this.order = order;
    }

    @Override
    public void show() {
        if(order!= null)
            order.show();
    }
}

各種餐點類別

public class Drink extends Item {

    private void addDrink(){
        System.out.println("一份飲料");
    }

    @Override
    public void show() {
        super.show();
        addDrink();
    }
}

public class MainMeal extends Item {

    private void addMeal(){
        System.out.println("加一份主餐");
    }

    @Override
    public void show() {
        super.show();
        addMeal();
    }
}

public class Salad extends Item {

    private void addSalad(){
        System.out.println("一份沙拉");
    }

    @Override
    public void show() {
        super.show();
        addSalad();
    }
}

public class Soup extends Item {

    private void addSoup(){
        System.out.println("加一份湯品");
    }

    @Override
    public void show() {
        super.show();
        addSoup();
    }
}

測試一下

public class Test {
    @org.junit.jupiter.api.Test
    public void test(){
        Order order = new Order();

        Drink drink = new Drink();
        MainMeal mainMeal = new MainMeal();
        Soup soup = new Soup();

        drink.decorate(order);
        mainMeal.decorate(drink);
        soup.decorate(mainMeal);

        soup.show();

    }
}

得到

一份飲料
加一份主餐
加一份湯品

這時候如果要實現套餐,套餐內部可以用裝飾模式實現,自由度就大很多。

試著實現套餐

public class Set extends Order {
    protected Order order;

    public void decorate(Order order){
        this.order = order;
    }

    @Override
    public void show() {
        if(order!= null)
            order.show();
    }
}

簡單套餐

public class SimpleSet extends Set {

    private void addSet(){
        Salad salad = new Salad();
        MainMeal mainMeal = new MainMeal();
        Drink drink = new Drink();
        salad.decorate(order);
        mainMeal.decorate(salad);
        drink.decorate(mainMeal);
        this.order = drink;
    }

    @Override
    public void show() {
        addSet();
        super.show();
    }
}

稍微測試一下

public class Test {
    @org.junit.jupiter.api.Test
    public void test(){

        SimpleSet simpleSet = new SimpleSet();
        simpleSet.show();

    }
}

得到

一份沙拉
加一份主餐
一份飲料

下面一段引用於「大話設計模式 」page.82

裝飾模式可以把類別中的裝飾功能從類別中搬移去除,這樣可以簡化原有類別。也就是把類別中核心職責和裝飾功能區分開,並去除相關內別中重複的邏輯。

今天就先研究到這邊,稍微消化一下...有什麼更好的想法可以留言或私訊我喔,感恩~


上一篇
[ Day 6 ] 初探設計模式 - 深入工廠、策略與單例模式
下一篇
[ Day 8 ] 初探設計模式 - 觀察者模式 ( Observer Pattern )
系列文
初探設計模式30

1 則留言

0
alantsui
iT邦新手 5 級 ‧ 2019-10-28 10:01:48

我想問下你的UML圖用什麼軟件生成的?

Daniel Wu iT邦新手 5 級 ‧ 2019-10-28 11:43:32 檢舉

這個我是用keynote自己畫的 XD

我要留言

立即登入留言