天天看點

《.NET應用架構設計:原則、模式與實踐》新書部落格--試讀-2.1.2 設計原則實戰

2.1.2  設計原則實戰

    下面我們就以一個簡單的電子商務系統為背景:通過給定的産品分類ID擷取該分類下的所有産品。

對于這個問題,基本上不用想就可以實作,如圖2-1的類圖設計。

《.NET應用架構設計:原則、模式與實踐》新書部落格--試讀-2.1.2 設計原則實戰

圖2-1  擷取分類産品的類圖

其中:

q  ProductService類被客戶程式調用,通過調用GetAllProductsFrom方法來擷取産品,并且此處還可以添加緩存政策或異常處理等機制。

q  Product類代表了産品的實體。

q  ProductRepository類用來從資料儲存設備(資料庫或XML等其他資料存儲器)中讀取産品資訊。     

注意  為了減少讀者不必要的麻煩,讓了解更加直覺,以後的章節中,都會給出完整的代碼示例和項目圖示。

在這個示例中,Visual Studio項目的結構圖如圖2-2所示。

《.NET應用架構設計:原則、模式與實踐》新書部落格--試讀-2.1.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 ,如需轉載請自行聯系原作者

繼續閱讀