Design Pattern (G4) - Composite Pattern

當一個物件結構為一種合成物件的樹狀結構時,就可以考慮使用Composite Pattern,舉個簡單的例子,一台電腦裡有CPU, 顯示卡, 硬碟, 光碟機...等,這些零件都算是電腦元件,每個電腦元件有價格, 製造廠商, 保固日期...等屬性,而電腦為這些電腦元件所組成,整台電腦的價格為底下電腦元件的價格總和。再往上一點我們也可以說電腦是組成雲端的電腦元件之一,這種層層包含的關係我們稱為Composite。


目的(Intent)

將合成物件結構以樹狀結構表現出它的階層關係,讓Client端能夠使用統一一致性的方式對合成物件進行處理。

動機(Motivation)

通常對合成物件的處理是在合成物件裡直接包含其他原生物件,每個原生物件皆各自獨立,有各自的屬性方法定義。這種情況下合成物件必須針對每個原生物件進行個別處理,當合成物件的階層數愈多,會使得整個結構變得更複雜,愈難處理底下原生物件的操作。

使用時機

  1. 當你想要簡單的呈現出物件之間的樹狀階層關係。
  2. 當你想要讓Client端能夠過一致性的方法處理合成物件架構中的物件。



結構(Structure)


上圖為Composite Pattern的基本結構,此結構主要由Component, Leaf與Composite三個部分組成。Component為Leaf與Composite的共通介面,它定義了一些共通的存取操作,實作基本行為,另外在此也可以宣告Composite遞迴存取Leaf的方法;Leaf為遞迴中最底層的元件,須明確的定義原生的行為與回傳值,否則Composite在遞迴時會因為不明確而進入無窮迴圈;Composite為Composite Pattern的精隨,它包含了許多Component,可能為Leaf或Composite物件,在此定義了一些管理底下Component的方法,透過遞迴的方式對底下的Component進行操作。Client為透過Component介面操作此Composite結構的物件。


實作(Implementation)

限量以組裝電腦為範例,一台PC內由許多種零件組成才能Work,例如:Memory, 硬碟, 主機板...等。再放大一點由Cluster來看,一個Cluster可由很多PC組成,由Hub透過網路溝通。所以我們可以將Rack(機架)與PC看成是一個Composite,而內部零件為Leaf,他們的共同繼承一個Equipment的介面,因為每個零件都有其價格, 廠牌, 壽命...等共有屬性,並且每個零件都會執行,所以就在Equipment介面裡先定義好這些共有屬性與方法,每個零件再根據自己的特色來實作。比較特別的是PC與Rack,因為他們為許多零件的集合,所以當某個零件壞掉時,就會影響到整體,所以在這裡PC與Rack的ExpireTime與ExpireTime最短的零件相同;另外,Rack與PC再呼叫執行的方法時會去驅動所有底下零件執行。

以下為範例程式碼:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace ConsoleTest
{
 public class Program
 {
  static void Main( string[] args )
  {
   var testDevice = InitailTestData();
   testDevice.Execute();
   Console.WriteLine( "\nRack expire time: {0}", testDevice.ExpireTime );

   Console.ReadLine();
  }

  static Rack InitailTestData()
  {
   var myPC1 = new PC( "ACER", new List<Equipment>() { 
    new Power(36, "PowerB", 700), 
    new MotherBoard(36, "ASUS", 2300), 
    new Disk(28, "EMC", 2000), 
    new Disk( 28, "EMC", 2000 ), 
    new CDRom( 48, "ROM", 800 ), 
    new Memory( 24, "Transcend", 800 ), 
    new Memory( 24, "Transcend", 800 ), 
    new PCICard( 16, "ATI", 3400 ) 
   } );

   var myPC2 = new PC( "MSI", new List<Equipment>()
   {
    new Power(48, "PowerA", 1000),
    new MotherBoard(32, "MSI", 1000),
    new Disk(12, "EMC", 1500),
    new CDRom( 13, "ROM", 800 ),
    new Memory( 24, "Transcend", 800 ),
   } );

   var hub = new Hub( 48, "D-Link", 1200 );
   var myRack = new Rack( "NoName", new List<Equipment>()
   {
    myPC1, myPC2, hub
   } );
   return myRack;
  }
 }

 public abstract class Equipment
 {
  public virtual int ExpireTime { get; set; }
  public string Brand { get; set; }
  public int Price { get; set; }

  public abstract void Execute();

  protected Equipment( int expire, string brand, int price ) : this(expire, brand, price, null)
  {
   
  }

  protected Equipment( string brand, List<Equipment> components )
   : this( 0, brand, 0, components )
  {

  }

  protected Equipment( int expire, string brand, int price,  List<Equipment> components )
  {
   ExpireTime = expire;
   Brand = brand;
   Price = price;
  }
 }

 public class Power : Equipment
 {
  public Power( int expire, string brand, int price )
   : base( expire, brand, price )
  {

  }

  public override void Execute()
  {
   Console.WriteLine( "Power is running" );
  }
 }

 public class MotherBoard : Equipment
 {
  public MotherBoard( int expire, string brand, int price )
   : base( expire, brand, price )
  {

  }

  public override void Execute()
  {
   Console.WriteLine( "MotherBoard is running" );
  }
 }

 public class Disk : Equipment
 {
  public Disk( int expire, string brand, int price )
   : base( expire, brand, price )
  {

  }

  public override void Execute()
  {
   Console.WriteLine( "Disk is running" );
  }
 }

 public class CDRom : Equipment
 {
  public CDRom( int expire, string brand, int price )
   : base( expire, brand, price )
  {

  }

  public override void Execute()
  {
   Console.WriteLine( "CDRom is running" );
  }
 }

 public class Memory : Equipment
 {
  public Memory( int expire, string brand, int price )
   : base( expire, brand, price )
  {

  }

  public override void Execute()
  {
   Console.WriteLine( "Memory is runnning" );
  }
 }

 public class Hub : Equipment
 {
  public Hub( int expire, string brand, int price )
   : base( expire, brand, price )
  {

  }

  public override void Execute()
  {
   Console.WriteLine( "Hub is running" );
  }
 }

 public class PCICard : Equipment
 {
  public PCICard( int expire, string brand, int price )
   : base( expire, brand, price )
  {

  }

  public override void Execute()
  {
   Console.WriteLine( "PCICard is running" );
  }
 }

 public class PC : Equipment
 {
  private List<Equipment> Components { get; set; }

  public PC( string brand, List<Equipment> equipments )
   : base( brand, equipments )
  {
   Components = equipments;
  }

  public override int ExpireTime
  {
   get
   {
    return Components.Min(p => p.ExpireTime);
   }
   set
   {
    base.ExpireTime = value;
   }
  }

  public override void Execute()
  {
   Parallel.ForEach( Components, e =>
   {
    e.Execute();
   } );
   Console.WriteLine( "PC is running" );
  }
 }

 public class Rack : Equipment
 {
  private List<Equipment> Components { get; set; }

  public Rack( string brand, List<Equipment> equipments )
   : base( brand, equipments )
  {
   Components = equipments;
  }

  public override int ExpireTime
  {
   get
   {
    return Components.Min(p => p.ExpireTime);
   }
   set
   {
    base.ExpireTime = value;
   }
  }

  public override void Execute()
  {
   Parallel.ForEach( Components, e =>
   {
    e.Execute();
   } );
   Console.WriteLine( "Rack is running" );
  }
 }
}


優點

  1. Client端能夠更簡單利用Component介面操作整個Composite結構。
  2. 可以簡單的增加Leaf原生物件,只要有實作Component共通的方法。

相關樣式(Related Pattern)

  1. Chain of Responsibility
  2. Decorator
  3. Flyweight
  4. Iterator
  5. Visitor

使用頻率











參考來源:
Design Patterns - Gang of Four



留言