聊聊C#中的composite模式

寫在前面

Composite組合模式屬於設計模式中比較熱門的一個,相信大家對它一定不像對訪問者模式那麼陌生,畢竟誰又沒有遇到過樹形結構呢。不過所謂溫故而知新,我們還是從一個例子出發,起底一下這個模式吧。
 

一個簡單例子

設想我們要建立一個公司的人事架構,在一個公司裏,我們可以簡單地分爲兩種員工,一種是經理(包括老闆),另一種是基層員工,經理可以有下屬,而普通員工不行,我們寫出這樣的代碼。
 

基層員工類

這種員工是最基層的員工,沒有下屬

class BasicLevelEmployee //基層員工
{
    public string ID { get; set; }
    public void ShowStatus(int indent)
    {
        string str = ID;
        str = str.PadLeft(ID.Length + indent, '-');
        Console.WriteLine(str);
    }
}

 

經理類

經理可以有下屬,下屬可能是基層員工,也可能是其他經理(考慮老闆這種情況,無疑其他經理也是老闆的下屬),因爲比基層員工多了下屬,所以也多了一些方法維護下屬屬性

class Manager //經理
{
    public string ID { get; set; }
    public void ShowStatus(int indent) 
    {
        string str = ID;            
        str = str.PadLeft(ID.Length + indent, '-');
        Console.WriteLine(str);
        indent += 4;
        Subordinate.ForEach(s => s.ShowStatus(indent));
        SubordinateManagers.ForEach(m => m.ShowStatus(indent));
    }
    public List<BasicLevelEmployee> Subordinate = new List<BasicLevelEmployee>();
    public List<Manager> SubordinateManagers = new List<Manager>();
    //下面是經理所屬的方法
    public void AddSubordinate(BasicLevelEmployee e) { Subordinate.Add(e); }
    public void AddSubordinate(Manager e) { SubordinateManagers.Add(e); }
    public void RemoveSubordinate(BasicLevelEmployee e) { Subordinate.Remove(e); }
    public void RemoveSubordinate(Manager e) { SubordinateManagers.Remove(e); }      
}

 

公司架構類

公司架構類非常簡單,只需要掌握最大的BOSS,整個公司人事架構都可以順藤摸瓜的展示出來

class CompanyHierachy
{
    public Manager BOSS { get; set; }
    public void ShowStatus()
    {
        BOSS.ShowStatus(0);
    }
}

客戶端代碼

假設這個公司的結構很單純,除了老闆就是開發部門和財務部門,各個部門分設經理是,所以我們寫出代碼如下

class Program
{
    static void Main(string[] args)
    {
        //老闆
        Manager boss = new Manager() { ID = "BOSS" };
        //開發部門經理
        Manager devManager = new Manager() { ID = "Dev Manager" };
        //財務部門經理
        Manager financeManager = new Manager() { ID = "Finance Manager" };
        //開發組長
        Manager devLead = new Manager() { ID = "Dev Lead" };
        //測試組長
        Manager qcLead = new Manager() { ID = "QC Lead" };

        boss.AddSubordinate(devManager);
        boss.AddSubordinate(financeManager);
        financeManager.AddSubordinate(new BasicLevelEmployee() { ID = "Purchase" });
        devManager.AddSubordinate(devLead);
        devManager.AddSubordinate(qcLead);
        devLead.AddSubordinate(new BasicLevelEmployee() { ID = "Developer1" });
        devLead.AddSubordinate(new BasicLevelEmployee() { ID = "Developer2" });
        qcLead.AddSubordinate(new BasicLevelEmployee() { ID = "QuanityControl1" });
        qcLead.AddSubordinate(new BasicLevelEmployee() { ID = "QuanityControl2" });
        CompanyHierachy company = new CompanyHierachy() { CEO = boss };
        company.ShowStatus();
    }
}

代碼非常簡單,不需要更多解釋了,運行後得到結果

一切正常,代碼是工作的,公司架構建立成功了。

再想一下

但是想想,這樣的代碼真的好嗎?感覺起碼有兩個地方我們可以改進。

  1. 基層員工和經理其實有太多的共性(屬性和方法),可以利用抽象思維,讓他們繼承自同一種東西嗎?
  2. 在經理類中我們維護了多個下屬列表,如果以後再加一個實習生,是不是我們又得創建更多的列表?如果我們使用了繼承,這個問題還會存在嗎?

基於此,利用抽象思維讓經理和員工繼承自同一個類(僱員)勢在必行。在抽象之後,經理類會繼承自僱員並且也內含僱員列表,可能第一次見到這種包含自身父類列表的設計方式會讓人感覺不習慣,但不用擔心,這其實是一種比較常見的設計方式。這種既有繼承也有合成的結構,就是組合模式的精髓。
 

使用組合模式進行重構

組合模式屬於結構型設計模式,它利用類型層級和聚合層級構造更大的複合結構

說的更加直白一點,當對象的局部結構和對象自身相同的情況下,我們可以使用繼承加上聚合的方式來組合代碼,比如剛剛提到的例子中,

觀察一下,對於Boss來說,它的局部結構,即DevManager和FinanceManager與它自己的結構有何區別?都是樹結構,無非就是根節點不一樣而已,所以於情於理這一塊可以用繼承加聚合來重構
那麼心細的朋友肯定發現了,有些操作是經理類獨有的,這些操作我們是應該抽象到和基層員工共同的父類僱員類嗎?對於這個問題,一般有兩種解決方案

透明型


在此設計中,子類方法的並集被提煉到了共有父類,哪怕這些方法對於某些子類根本不需要,這樣的好處是客戶端在使用的時候根本不需要知道對象糾結是哪個子類,對客戶端透明,所以得名。當前設計多采用這種。

安全型


安全型設計非常保守,只會提煉子類交集的方法到父類,這樣的好處是絕對安全,客戶端絕對不可能在BasicLevelEmployee對象上面調用AddSubordinate或者RemoveSubordinate。但有時候會面臨向下轉型的情況。
 

重構後的代碼(透明型)

抽象出共同父類僱員類,使用透明型,所有的子類方法都提煉到這個類

abstract class Employee
{
    public string ID { get; set; }
    public abstract void ShowStatus(int indent);
    //因爲是透明型,所以基層員工用不上的方法也會被抽象到父類
    public abstract void AddSubordinate(Employee e);
    public abstract void RemoveSubordinate(Employee e);
}

對於基層員工,如果客戶端無意間調用了不該使用的方法,這基本是一個明確的、表明客戶端代碼出現了邏輯問題的信號,這種情況直接拋出異常,能更快地暴露出問題

class BasicLevelEmployee : Employee
{
    public override void ShowStatus(int indent)
    {
        string str = ID;
        str = str.PadLeft(ID.Length + indent, '-');
        Console.WriteLine(str);
    }

    public override void AddSubordinate(Employee e)
    {
        throw new NotImplementedException();
    }
    public override void RemoveSubordinate(Employee e)
    {
        throw new NotImplementedException();
    }
}

在經理類中,得益於共有父類Employee,我們可以用一個列表裝下所有的下屬,不論下屬是基層員工,還是經理,抑或是未來可能添加的實習生。畢竟他們都是僱員嘛

class Manager : Employee
{
    public override void ShowStatus(int indent)
    {
        string str = ID;
        str = str.PadLeft(ID.Length + indent, '-');
        Console.WriteLine(str);
        indent += 4;
        Subordinate.ForEach(s => s.ShowStatus(indent));
    }
    public List<Employee> Subordinate = new List<Employee>();
    //下面是經理所屬的方法
    public override void AddSubordinate(Employee e) { Subordinate.Add(e); }
    public override void RemoveSubordinate(Employee e) { Subordinate.Remove(e); }
}

公司架構類和客戶端代碼調用保持不變,運行結果一致,重構成功。
 
可以看到,在使用了組合模式之後,現在的代碼不但消除了冗餘(不用再去維護多個下屬列表),也更具有抵禦未來變化的能力,這樣的結構比起原來,當然是更加合理的。這就是結構型設計模式的用武之地,讓對象的結構更加的合理,更加的易於擴展。
這就是關於Composite組合模式的介紹,鑑於筆者能力有限,如果大家對於這篇文章中所講有其他看法,歡迎留言討論。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章