2.1.2 設計原則實戰
下面我們就以一個簡單的電子商務系統為背景:通過給定的産品分類ID擷取該分類下的所有産品。
對于這個問題,基本上不用想就可以實作,如圖2-1的類圖設計。

圖2-1 擷取分類産品的類圖
其中:
q ProductService類被客戶程式調用,通過調用GetAllProductsFrom方法來擷取産品,并且此處還可以添加緩存政策或異常處理等機制。
q Product類代表了産品的實體。
q ProductRepository類用來從資料儲存設備(資料庫或XML等其他資料存儲器)中讀取産品資訊。
注意 為了減少讀者不必要的麻煩,讓了解更加直覺,以後的章節中,都會給出完整的代碼示例和項目圖示。
在這個示例中,Visual Studio項目的結構圖如圖2-2所示。
圖2-2 Visual Studio解決方案圖
正如之前所說:ProductService提供外部調用的方法接口,并且采用了緩存政策和異常處理機制,來提高程式的性能和健壯性,代碼如下所示:
using System;
usingSystem.Collections.Generic;
usingSystem.Web;
using AgileSharp.Chapter2.Repository;
using AgileSharp.Chapter2.Domain;
namespace AgileSharp.Chapter2.Service
{
public class ProductService
{
private ProductRepository productRepository = null;
public ProductService()
{
productRepository = new ProductRepository();
}
public List<Product> GetAllProductsFrom(intcategoryId)
List<Product> result = new List<Product>();
try
{
string cacheKey = string.Format("products_in_category_{0}",categoryId);
result = HttpContext.Current.Cache.Get(cacheKey) as List<Product>;
if (result == null)
{
result = productRepository.GetProductsFrom(categoryId);
if (result != null &&result.Count> 0)
{
HttpContext.Current.Cache.Insert(cacheKey, result);
}
}
}
catch (Exception ex)
//Log will add here later
return result;
}
}
至于Product和ProductRepository的代碼就非常簡單了,這裡不多解釋。
示例到這裡,也許大家有個疑問:上面的代碼有些什麼問題?
q ProductService依賴于ProductRepository。在沒有任何變化的情況下,這沒有什麼問題。但是如果現在項目換了資料儲存設備,例如将資料庫換成了XML檔案,或者資料庫的通路技術從ADO.NET 換成了Entity Framework,那麼ProductRepository的代碼就得改變,這會使得整個項目都需要重新編譯,重新部署。問題來了:此時系統中隻有ProductRepository一個變化點,為什麼非得要求整個項目重新編譯,重新部署呢?難道不能隻重新編譯和部署那個變化的子產品呢?
q 代碼不具有測試性能。要想知道此段功能代碼是否按照了我們的意願運作,可以通過人工稽核,然後通過GUI界面擷取資料來進行調試,此時的邏輯相對而言比較簡單,此方法也還行得通。不過,一旦業務邏輯變得複雜或代碼量的劇增,那麼很難確定代碼不會出錯,而這些錯誤很多時候隻會在運作時才能被發現。
q 緩存機制依賴于HttpContext,這不僅僅會讓測試産生困難(盡管可以有Mock),而且會對後續系統的擴充有阻礙(例如采用分布式緩存)。
對以上的問題進行進一步分析,可以知道,這都是因為違背了以下設計原則:
q ProductService依賴了ProductRepository的具體實作,而ProductRepository是一個可能的變化點。也就是說:ProductService這個高層子產品依賴了ProductRepository底層子產品,違背了依賴倒置原則,這也就使得一個ProductRepository變化,整個項目都需要重新編譯,重新部署。同理,緩存機制也是。
q 對于可測試性的問題,嚴格來說,上面的代碼是可以測試的,但是測試的時候必須依賴于外部的資料儲存設備,例如資料庫,那麼測試的結果可能會由于資料的變動而不一樣,而且每次測試所花的時間也會比較長。
接下來嘗試重構上面的代碼,讓代碼的組織方式更加的靈活和易于擴充。
既然上面代碼主要是違背了DIP依賴倒置原則(再次回顧一下DIP:依賴抽象,而不依賴具體實作)。
那麼現在就提出接口IProductRepository,使得ProductService依賴這個接口,代碼如下:
using System.Collections.Generic;
namespace AgileSharp.Chapter2.Repository
public interface IProductRepository
List<Product> GetProductsFrom(intcategoryId);
現在接口已經抽象出來了,ProductService可以直接依賴接口了,但是我們現在還需要一個實作IProductRepository接口的類ProductRepository,然後再采用LSP(裡氏替換原則),用ProductRepository替換。代碼如下:
public class ProductRepository:IProductRepository
//…
現在ProductService的代碼如下所示:
privateIProductRepositoryproductRepository = null;
publicProductService()
{
//...
}
其實可以看到,上面ProductService的代碼雖然提出了抽象接口,但問題依然存在:仍依賴于ProductRepository的具體實作。
問題到這裡就好辦了,可以采用工廠模式,通過讀取配置檔案進行反射或采用依賴注入等方法得到解耦。
可以看出:設計原則解決了變化點的問題,将ProductRepository這個變化點從ProductService中移出,然後一步步的遷移,最後把這個變化點引到了配置檔案中,也就是将變化點引到了系統之外,也許這些正是我們需要的。
本文轉自yanyangtian51CTO部落格,原文連結:http://blog.51cto.com/yanyangtian/758289 ,如需轉載請自行聯系原作者