Design Pattern - Decorator

(史帝芬, 2007/04/23, hi.steven@gmail.com)
    試著想一下,如果我們要寫一個牛排館的計費程式,這家牛排館有兩樣主菜及一些附餐, 主菜為牛排、豬排,附餐有麵、生菜沙拉、飲料、甜點,客人點菜時,需點一樣主菜,附 餐可多選也可不點,計費的方式就像自助餐一樣,每樣菜有它的價格,相加後即為客人要 付的費用。我們希望未來這家牛排館增加主菜或附餐時,都不需要修改既有程式! 可能嗎? 下面我們來看看Decorator Pattern如何滿足這樣的需求!
  • 程式 (C#)

  • using System;
    
    namespace Decorator
    {
        abstract class Meal
        {
            protected string description = "排餐";
    
            public virtual string Description
            {
                get { return description; }
                set { description = value; }
            }
    
            abstract public int cost();
        }
    
        class PorkChop : Meal
        {
            public PorkChop()
            {
                Description = "豬排";
            }
    
            public override int cost()
            {
                return 130;
            }
        }
    
        class Steak : Meal
        {
            public Steak()
            {
                Description = "牛排";
            }
    
            public override int cost()
            {
                return 150;
            }
        }
    
        abstract class Condiment : Meal
        {
            protected Meal meal;
    
            public override string Description
            {
                get { return meal.Description + " + " + description; }
                set { description = value; }
            }
        }
    
        class Noodle : Condiment
        {
            public Noodle(Meal meal)
            {
                Description = "麵";
                this.meal = meal;
            }
    
            public override int cost()
            {
                return 50 + meal.cost();
            }
        }
    
        class Salad : Condiment
        {
            public Salad(Meal meal)
            {
                Description = "生菜沙拉";
                this.meal = meal;
            }
    
            public override int cost()
            {
                return 60 + meal.cost();
            }
        }
    
        class Drink : Condiment
        {
            private bool isHot;
    
            public bool IsHot
            {
                get { return isHot; }
            }
    
            public Drink(Meal meal, bool isHot)
            {
                Description = (isHot) ? "熱飲" : "冷飲";
                this.meal = meal;
                this.isHot = isHot;
            }
    
            public override int cost()
            {
                return (isHot) ? (30 + meal.cost()) : (50 + meal.cost());
            }
        }
    
        class Dessert : Condiment
        {
            public Dessert(Meal meal)
            {
                Description = "甜點";
                this.meal = meal;
            }
    
            public override int cost()
            {
                return 40 + meal.cost();
            }
        }
    
        class Program
        {
            static void Main(string[] args)
            {
                Meal meal = new PorkChop();
                Console.WriteLine(meal.Description + " = NT$ " + meal.cost());
    
                Meal meal2 = new Noodle(meal);
                Console.WriteLine(meal2.Description + " = NT$ " + meal2.cost());
    
                Meal meal3 = new Drink(meal2, true);
                Console.WriteLine(meal3.Description + " = NT$ " + meal3.cost());
    
                Meal meal4 = new Dessert(meal3);
                Console.WriteLine(meal4.Description + " = NT$ " + meal4.cost());
    
                Console.ReadLine();
            }
        }
    }
    

  • 執行結果

  • 豬排 = NT$ 130
    豬排 + 麵 = NT$ 180
    豬排 + 麵 + 熱飲 = NT$ 210
    豬排 + 麵 + 熱飲 + 甜點 = NT$ 250
    

  • 說明

  •     由上面的程式可以了解,未來要增加主菜,只要寫一個和牛排、豬排一樣的class繼承Meal ; 要增加附餐,則如飲料、生菜沙拉一樣,寫個繼承Condiment的class即可。這個Pattern最有趣的是,Condiment既繼承了 Meal,也合成Meal,其實這裡的繼承只是為了讓配菜和主菜同型別,功能上是用合成取得的。在物件導向 程式設計裡,為了降低兩個類別間的耦合度,使用合成取代繼承是常見的手法。 在Java I/O裡也可以看到Decorator Pattern的運用。
        在研究這個Pattern時,感謝Michael Tsai解答了我心中的疑惑, Microsoft Forum