邁向架構師的暖身運動(2):抽象化的能力

一般在寫程式的時候,往往都是要先探詢寫這支程式的需求是什麼,如果有些工作是由流程 (process) 構成的,或者是這件工作可能會橫跨不同的模組(或資料庫),又或者是這個程式預期未來可能會有什麼樣的衍生功能時,就可以試著把這些程式中共同的部份加以抽出,獨立構成一個公用程式庫 (utility) 或是基礎類別 (base class),而將這些部份抽出的流程即稱為抽象化 (abstraction)。

一般在寫程式的時候,往往都是要先探詢寫這支程式的需求是什麼,如果有些工作是由流程 (process) 構成的,或者是這件工作可能會橫跨不同的模組(或資料庫),又或者是這個程式預期未來可能會有什麼樣的衍生功能時,就可以試著把這些程式中共同的部份加以抽出,獨立構成一個公用程式庫 (utility) 或是基礎類別 (base class),而將這些部份抽出的流程即稱為抽象化 (abstraction),以前在大學時如果是資訊類科系的人,應該都會修過一門資料結構(Data Structure)的課程吧,這個課程在一開始就傳授資料抽象化以及抽象類別 (abstract class) 的概念,這個概念在實戰過程中很好用,而且如果抽象化做的愈好,對程式的彈性有顯著的幫助。

與抽象化相應的名詞則是一般化 (Generalization),透過一般化的程序,可以讓程式碼可以適用於特定的環境與流程中,就像統計學的 LISREL (線性關聯分析),將多變量分析 (Multi-variate statistical Analysis) 整合成一個數學程序一樣,只要利用這個數學程序,就可以計算出各種多變量的統計數據一樣。一般化程序可以將一套技術或一套實作方針固定下來,讓使用架構的開發人員只要依照已被一般化的程序,即可做出需要的功能。

抽象化則是有助於釐清並打定基礎,例如某個專案要求建立產品設定檔,其產品分為三種產品線,而這三種產品線除了基本的名稱,條碼與說明以外,其他的欄位都不同,而且還有可能是空值時,這時就可能要考量:

  • 未來還可不可能有第四種。
  • 欄位的擴充性。
  • 模組在產品擴充時是否仍具可插拔能力。

因此,類別可以這樣設計:

public abstract class ProductBase
{
   protected string ProductID { get; set; }
   protected string Barcode { get; set; }
   protected string Caption { get; set; }
   protected string Description { get; set; }

   public abstract void Create();
   public abstract void Update();
   public abstract void Delete();
   public abstract void Get(string ProductID);
   public abstract int QueryStock();

}

 

接著,如果需要定義自己的產品資料檔時,就可以利用這個類別來繼承,並產生出自己的類別,例如:

// 定義書籍的產品類別
public class BookProduct : ProductBase
{
   public string ProductID { get { return base.ProductID; } }
   public string Barcode { get { return base.Barcode; } set { base.Barcode = value; } }
   public string Caption { get { return base.Caption ; } set { base.Caption = value; } }
   public string Description { get { return base.Description; } set { base.Description = value; } }
   public string ISBN { get; set; } // property for BookProduct only. 

   public string Author { get; set; } // property for BookProduct only.
   public string Title { get; set; } // property for BookProduct only.
   public DateTime PublishDate { get; set; } // property for BookProduct only.
      

   public void Create() {...}
   public void Update() {...}
   public void Delete() {...}
   public void Get(string ProductID) {...}
   public int QueryStock() {...}
   public BookProduct GetByISBN(string ISBN) {...} // method for BookProduct only.
   public BookProduct[] ListByAuthor(string Author) {...} // method for BookProduct only.
   public BookProduct[] ListByTitle(string Title) {...} // method for BookProduct only.
}

// 定義服飾的產品類別。
public class ClothesProduct : ProductBase
{
   public string ProductID { get { return base.ProductID; } }
   public string Barcode { get { return base.Barcode; } set { base.Barcode = value; } }
   public string Caption { get { return base.Caption ; } set { base.Caption = value; } }
   public string Description { get { return base.Description; } set { base.Description = value; } }
   public string Size { get; set; } // property for ClothesProduct only. 

   public string Color { get; set; } // property for ClothesProduct only.
   public string Texture { get; set; } // property for ClothesProduct only.
    

   public void Create() {...}
   public void Update() {...}
   public void Delete() {...}
   public void Get(string ProductID) {...}
   public int QueryStock() {...}
   public ClothesProduct[] ListByColor(string Color) {...} // method for ClothesProduct only.
   public ClothesProduct[] ListByTexture(string Texture) {...} // method for ClothesProduct only.
}

如此一來,只要有一個基底的類別,就可以不斷的衍生出新的子類別,這些類別都擁有共同的特性,而且功能是由各個子類別來顯露,用戶端只要針對基底類別來操作即可,或是在需要時將它轉型成子類別物件,並存取子類別中的方法。

ProductBase product = ProductBaseFactory.CreateProduct(typeof(BookProduct));
product.Get(productID);
int qty = product.QueryStock(); // call property on ProductBase class
string isbn = ((BookProduct)product).ISBN; // call property on BookProduct class

(備註:以上使用到 Prototype Pattern 和 Abstract Factory Pattern 兩種設計模式)

抽象化的能力對於系統設計 (system design) 是很重要的一環,架構師的位階在系統設計師之上,理所當然也要會這種分析與設計的模式,這樣有助於在功能與資料面上的切割以及保持最高的彈性。