設計模式之組合模式(Composite)分享

markriver發表於2021-09-09

場景:

如果想模擬windows的開始選單,分析一下會發現裡面的選單項:有些有子選單,有些則沒有;因此大體可以將選單類分為二類,設計程式碼如下:

/// 
    /// 選單的顯示介面
    /// 
    public interface IMenu
    {
        void Show();
    }
 
    /// 
    /// 選單基類
    /// 
    public class MenuBase
    {
        public string MenuName { set; get; }
    }
 
    /// 
    /// 有子選單的選單類
    /// 
    public class Menu : MenuBase, IMenu
    {
        public void Show()
        {
            Console.WriteLine(MenuName);
        }
 
        private IList _children;
 
        public IList Children
        {
            get
            {
                if (_children == null)
                {
                    _children = new List();
                }
                return _children;
            }
        }
    }
 
    /// 
    /// 無子選單的選單類(即最底級選單)
    /// 
    public class SingleMenu : MenuBase, IMenu
    {
        public void Show()
        {
            Console.WriteLine( MenuName);
        }
    }


客戶端示例呼叫如下:

class Program
    {
        //客戶程式
        static void Main(string[] args)
        {
            Menu menuContainer = new Menu() { MenuName = "開始" };
            SingleMenu menuShutdown = new SingleMenu() { MenuName = "t關機" };
            Menu menuProgram = new Menu() { MenuName = "t程式" };
            SingleMenu menuStartUp = new SingleMenu() { MenuName = "tt啟動" };
 
            menuProgram.Children.Add(menuStartUp);
            menuContainer.Children.Add(menuProgram);
            menuContainer.Children.Add(menuShutdown);
 
            menuContainer.Show();
            foreach (IMenu item in menuContainer.Children)
            {
                if (item is SingleMenu)
                {
                    item.Show();
                }
                else if (item is Menu)
                {
                    ShowAllSubMenu(item);
                }
            }
            Console.ReadLine();
        }
 
 
        /// 
        /// 客戶端呼叫的遞迴Show程式
        /// 
        /// 
        static void ShowAllSubMenu(IMenu menu)
        {
            if (menu is SingleMenu)
            {
                menu.Show();
            }
            else if (menu is Menu)
            {
                menu.Show();
                IList children = (menu as Menu).Children;
                foreach (IMenu i in children)
                {
                    ShowAllSubMenu(i);
                }
                 
            }
        }
    }


從功能正確性上講,上面的示意程式碼並無大錯,但是如果從客戶程式上考慮,卻發現這樣並非最佳實踐:客戶程式依賴了太多的Menu類細節,客戶程式在樹型選單建立完成後,最關心的莫過於如何把選單完整的顯示出來,但上面的程式碼中為了達到這個目的,卻不得不知道子選單的內部實現(透過Children和型別判斷),如果以後選單類升級,修改了內部構造(比如將Children改成GetChildren),客戶程式將被迫重新修改,這時候組合(Composite)模式就派上用場了。

using System;
using System.Collections.Generic;
 
namespace composite
{
    class Program
    {
        //客戶程式
        static void Main(string[] args)
        {
            IMenu menuContainer = new Menu() { MenuName = "開始" };
            IMenu menuShutdown = new SingleMenu() { MenuName = "t關機" };
            IMenu menuProgram = new Menu() { MenuName = "t程式" };
            IMenu menuStartUp = new SingleMenu() { MenuName = "tt啟動" };
 
            menuProgram.Add(menuStartUp);
            menuContainer.Add(menuProgram);
            menuContainer.Add(menuShutdown);
 
            menuContainer.Show();           
             
            Console.ReadLine();
        }
        
    }
 
    /// 
    /// 選單的顯示介面
    /// 
    public interface IMenu 
    {
        void Show();
        void Add(IMenu menu);
        void Remove(IMenu menu);
    }
 
    /// 
    /// 選單基類
    /// 
    public abstract class MenuBase 
    {
        public string MenuName { set; get; }
        protected IList Children;   
    }
 
    /// 
    /// 有子選單的選單類
    /// 
    public class Menu : MenuBase,IMenu
    {        
 
        public void Show()        
        {            
            ShowAllSubMenu(this);
        }
 
        public void ShowAllSubMenu(IMenu menu)
        {
            if (menu is SingleMenu)
            {
                menu.Show();
            }
            else if (menu is Menu)
            {
                Console.WriteLine((menu as Menu).MenuName);
                IList children = (menu as Menu).Children;
                foreach (IMenu i in children)
                {
                    ShowAllSubMenu(i);
                }                
            }
        }
         
 
        public void Add(IMenu menu)
        {
            if (Children == null) { Children = new List(); }
 
            Children.Add(menu);
        }
 
        public void Remove(IMenu menu)
        {
            if (Children == null) { Children = new List(); }
 
            Children.Add(menu);
        }
        
    }
 
    /// 
    /// 無子選單的選單類(即最底級選單)
    /// 
    public class SingleMenu :MenuBase, IMenu 
    {
        public void Show()
        {
            Console.WriteLine( MenuName);           
        }
 
        public void Add(IMenu menu)
        {
            throw new Exception("最底層選單無法Add子元素!");
        }
 
        public void Remove(IMenu menu) 
        {
            throw new Exception("最底層選單無法Remove子元素!");
        }
    }
}

最後再來理解Composite的意圖可能就更清楚了(摘自terrylee的“.NET設計模式(11):組合模式(Composite Pattern)"):

概述

組合模式有時候又叫做部分-整體模式,它使我們樹型結構的問題中,模糊了簡單元素和複雜元素的概念,客戶程式可以向處理簡單元素一樣來處理複雜元素,從而使得客戶程式與複雜元素的內部結構解 耦。

意圖

將物件組合成樹形結構以表示“部分-整體”的層次結構。Composite模式使得使用者對單個物件和組合物件的使用具有一致性。[GOF 《設計模式》]

 

類圖:

圖片描述

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/818/viewspace-2800142/,如需轉載,請註明出處,否則將追究法律責任。

相關文章