Asp.net設計模式筆記之一:理解設計模式

GOF設計模式著做中的23種設計模式能夠分紅三組:建立型(Creational),結構型(Structural),行爲型(Behavioral)。下面來作詳細的剖析。web

建立型算法

建立型模式處理對象構造和引用。他們將對象實例的實例化責任從客戶代碼中抽象出來,從而讓代碼保持鬆散耦合,將建立複雜對象的責任放在一個地方,這遵循了單一責任原則和分離關注點原則。數據庫

下面是「建立型」分組中的模式:設計模式

1.Abstract Factory(抽象工廠)模式:提供一個接口來建立一組相關的對象。緩存

2.Factory Method(工廠方法)模式:支持使用一個類來委託建立有效對象的責任。框架

3.Builder(生成器)模式:將對象自己的構造分離出來,從而可以構造對象的不一樣版本。分佈式

4.Prototype(原型)模式:可以從一個原型實例來複制或克隆類,而不是建立新實例。學習

5.Singleton(單例)模式:支持一個類只實例化一次,並只有一個可用來訪問它的全局訪問點。測試

結構型ui

結構型模式處理對象的組合與關係,以知足大型系統的須要。

下面是「結構型」分組中的模式:

1.Adapter(適配器)模式:使不兼容接口的類可以一塊兒使用。

2.Bridge(橋接)模式:將抽象與其實現分離,容許實現和抽象彼此獨立地改變。

3.Composite(組合)模式:能夠像對待對象的單個實例那樣來對待一組表示層次結構的對象。

4.Decorator(裝飾)模式:可以動態包裝一個類並擴展其行爲。

5.Facade(門面)模式:提供一個簡單的接口並控制對一組複雜接口和子系統的訪問。

6.Flyweight(享元)模式:提供一種在許多小類之間高效共享數據的方式。

7.Proxy(代理)模式:爲一個實例化成本很高的更復雜的類提供一個佔位符。

行爲型

行爲型模式處理對象之間在責任和算法方面的通訊。這個分組中的模式將複雜行爲封裝起來並將其從系統控制流中抽象出來,這樣就使複雜系統更容易理解和維護。

下面是」行爲型「分組中的模式:

1.Chain Of Responsibility(責任鏈)模式:容許將命令動態連接起來處理請求。

2.Command(命令)模式:將一個方法封裝成一個對象,並將該命令的執行與它的調用者分離。

3.Interpreter(解釋器)模式:指定如何執行某種語言中的語句。

4.Iterator(迭代器)模式:提供以形式化的方式來導航集合的方法。

5.Mediator(中介者)模式:定義一個對象,可讓其餘兩個對象進行通訊而沒必要讓它們知道彼此。

6.Memento(備忘錄)模式:容許將對象恢復到之前的狀態。

7.Observer(觀察者)模式:定義一個或多個類在另外一個類發生變化時接到報警。

8.State(狀態)模式:容許對象經過委託給獨立的,可改變的狀態對象來改變本身的行爲。

9.Strategy(策略)模式:可以將算法封裝到一個類中並在運行時轉換,以改變對象的行爲。

10.Template Method(模板方法)模式:定義算法流程控制,但容許子類重寫或實現執行步驟。

11.Vistor(訪問者)模式:可以在類上執行新的功能而不影響類的結構。

上面介紹了衆多的設計模式及其分組。可是如何來選擇和運用呢?下面有一些須要注意的事項:

1.在不瞭解模式的狀況下不能運用他們。

2.在設計的時候,要衡量是否有必要引入設計模式的複雜性。最好能衡量下實現某種模式所需的時間與該模式可以帶來的效益。謹記KISS原則:保持簡單淺顯。

3.將問題泛化,以更抽象的方式識別正在處理的問題。設計模式是高層次的解決方案,試着把問題抽象,並且不要過於關注具體問題的細節。

4.瞭解具備相似性質的模式以及同組中的其餘模式。之前已經使用過某個模式並不意味着在解決問題時它老是正確的模式選擇。

5.封裝變化的部分。瞭解應用程序中什麼可能發生變化。若是知道某個特殊的報價折扣算法將隨時間發生變化,那麼尋找一種模式來幫助您在不影響應用程序其他部分的狀況下改變該算法。

6.在選擇好設計模式以後,確保在命名解決方案中的參與者時使用該模式的語言及領域語言。例如,若是正在使用策略模式爲不一樣的快遞公司計價提供解決方案,那麼相應地爲他們明明,如FedExShippingCostStrategy。經過組合使用模式的公共詞彙表和領域語言,會讓代碼更具可讀性,並且更可以讓其餘具有模式知識的開發者理解。

 

就設計模式而言,除了學習以外沒有其餘替代方法。對每種設計模式瞭解得越多,在運用他們時就會準備的更好。當遇到一個問題正在尋找解決方案時,掃描一下每種模式的目的,喚起本身的記憶。

一種很好地學習方法就是試着識別.net框架中的模式,好比:Asp.net Cache使用了Singleton模式,在建立新的Guid實例時使用了Factory Method模式,.Net 2 xml類使用Factory Method模式,而1.0版並無使用。

 

下面咱們以一個快速模式示例來進行講解,以便於加深映像。

新建一個類庫項目0617.DaemonPattern.Service,而後引用System.web程序集。

首先添加一個Product.cs的空類做爲咱們的Model:

   public class Product
    {

    }

而後添加ProductRepository.cs類做爲咱們的數據存儲倉庫,從這裏咱們能夠從數據庫獲取數據實體對象:

    public class ProductRepository
    {
        public IList<Product> GetAllProductsIn(int categoryId)
        {
            var products = new List<Product>();
            //Database operation to populate products.
            return products;
        }
    }

最後添加一個名稱爲ProductService.cs的類,代碼以下:

 public class ProductService
    {
        public ProductService()
        {
            this.productRepository = new ProductRepository();
        }

        private ProductRepository productRepository;

        public IList<Product> GetAllProductsIn(int categoryId)
        {
            IList<Product> products;
            string storageKey = string.Format("products_in_category_id_{0}", categoryId);
            products = (List<Product>)HttpContext.Current.Cache.Get(storageKey);
            if (products == null)
            {
                products = productRepository.GetAllProductsIn(categoryId);
                HttpContext.Current.Cache.Insert(storageKey, products);
            }
            return products;
        }
    }

 

 

從代碼的邏輯,咱們能夠清楚的看到,ProductService經過ProductRepository倉庫從數據庫獲取數據。

這個類庫帶來的問題有如下幾點:

1.ProductService依賴於ProductRepository類。若是ProductRepository類中的API發生改變,就須要在ProductService類中進行修改。

2.代碼不可測試。若是不讓真正的ProductRepository類鏈接到真正的數據庫,就不能測試ProductService的方法,由於這兩個類之間存在着緊密耦合。另外一個與測試有關的問題是,該代碼依賴於使用Http上下文來緩存商品。很難測試這種與Http上下文緊密耦合的代碼。

3.被迫使用Http上下文來緩存。在當前狀態,若使用Velocity或Memcached之類的緩存存儲提供者,則須要修改ProductService類以及全部其餘使用緩存的類。Verlocity和Memcached都是分佈式內存對象緩存系統,能夠用來替代Asp.net的默認緩存機制。

隨意,綜上看來,代碼耦合度太高,不易進行測試,同時也不易進行替換。

 

既然知道了存在的問題,那麼就讓咱們來對其進行重構。

首先,考慮到ProductService類依賴於ProductRepository類的問題。在當前狀態中,ProductService類很是脆弱,若是ProductRepository類的API改變,就須要修改ProductService類。這破壞了分離關注點和單一職責原則

1.依賴倒置原則(依賴抽象而不要依賴具體)

能夠經過依賴倒置原則來解耦ProductService類和ProductRepository類,讓它們都依賴於抽象:接口。

在ProductRepository類上面右擊,選擇「重構」->「提取接口」選項,會自動給咱們生成一個IProductRepository.cs類:

public interface IProductRepository
    {
        IList<Product> GetAllProductsIn(int categoryId);
    }

修改現有的ProductRepository類,以實現新建立的接口,代碼以下:

 public class ProductRepository : IProductRepository
    {
        public IList<Product> GetAllProductsIn(int categoryId)
        {
            var products = new List<Product>();
            //Database operation to populate products.
            return products;
        }
    }

 

以後更新ProductService類,以確保它引用的是接口而非具體:

public class ProductService
    {
        public ProductService()
        {
            this.productRepository = new ProductRepository();
        }

        private IProductRepository productRepository;

        public IList<Product> GetAllProductsIn(int categoryId)
        {
            IList<Product> products;
            string storageKey = string.Format("products_in_category_id_{0}", categoryId);
            products = (List<Product>)HttpContext.Current.Cache.Get(storageKey);
            if (products == null)
            {
                products = productRepository.GetAllProductsIn(categoryId);
                HttpContext.Current.Cache.Insert(storageKey, products);
            }
            return products;
        }
    }

這樣修改以後,ProductService類如今只依賴於抽象而不是具體的實現,這意味着ProductService類徹底不知道任何實現,從而確保它不是那麼容易的被破壞掉,並且代碼在總體上說來對變化更有彈性。

可是,這裏還有個問題,既是ProductService類仍然負責建立具體的實現。並且目前在沒有有效的ProductRepository類的狀況下不可能測試代碼。因此這裏咱們須要引入另外一個設計原則來解決這個問題:依賴注入原則。

因爲ProductService類仍然與ProductRepository的具體實現綁定在了一塊兒,經過依賴注入原則,咱們能夠將這一過程移到外部進行,具體方法就是經過該類的構造器將其注入:

public class ProductService
    {
        public ProductService(IProductRepository productRepository)
        {
            this.productRepository = productRepository;
        }

        private IProductRepository productRepository;

        public IList<Product> GetAllProductsIn(int categoryId)
        {
            IList<Product> products;
            string storageKey = string.Format("products_in_category_id_{0}", categoryId);
            products = (List<Product>)HttpContext.Current.Cache.Get(storageKey);
            if (products == null)
            {
                products = productRepository.GetAllProductsIn(categoryId);
                HttpContext.Current.Cache.Insert(storageKey, products);
            }
            return products;
        }
    }

這樣就能夠在測試期間向ProductService類傳遞替代者,從而可以孤立地測試ProductService類。經過把獲取依賴的責任從ProductService類中移除,可以確保ProductService類遵循單一職責原則:它如今只關心如何協調從緩存或資源庫中檢索數據,而不是建立具體的IProductRepository實現。

依賴注入有三種形式:構造器,方法以及屬性。咱們這裏只是使用了構造器注入。

固然,如今的代碼看上去基本沒問題了,可是一旦替換緩存機制的話,將會是一個比較棘手的問題,由於基於Http上下文的緩存沒有被封裝,替換其須要對當前類進行修改。這破壞了開放封閉原則:對擴展開放,對修改關閉。

因爲Adapter(適配器)模式主要用來將一個類的某個轉換成一個兼容的接口,因此在當前的例子中,咱們能夠將HttpContext緩存API修改爲想要使用的兼容API。而後可使用依賴注入原則,經過一個接口將緩存API注入到ProductService類。

這裏咱們建立一個名爲ICacheStorage的新街口,它包含有以下契約:

 public interface ICacheStorage
    {
        void Remove(string key);
        void Store(string key, object data);
        T Retrieve<T>(string key);
    }

在ProductService類中,咱們就能夠將其取代基於HttpContext的緩存實例:

public class ProductService
    {
        public ProductService(IProductRepository productRepository,ICacheStorage cacheStroage)
        {
            this.productRepository = productRepository;
            this.cacheStroage = cacheStroage;
        }

        private IProductRepository productRepository;
        private ICacheStorage cacheStroage;

        public IList<Product> GetAllProductsIn(int categoryId)
        {
            IList<Product> products;
            string storageKey = string.Format("products_in_category_id_{0}", categoryId);
            products = cacheStroage.Retrieve<List<Product>>(storageKey);
            if (products == null)
            {
                products = productRepository.GetAllProductsIn(categoryId);
                cacheStroage.Store(storageKey, products);
            }
            return products;
        }
    }

而具體的緩存類咱們能夠繼承自ICacheStorage來實現:

public class HttpCacheAdapterStorage:ICacheStorage
    {
        public void Remove(string key)
        {
            if (HttpContext.Current.Cache[key] != null)
                HttpContext.Current.Cache.Remove(key);
        }

        public void Store(string key, object data)
        {
            if (HttpContext.Current.Cache[key] != null)
                HttpContext.Current.Cache.Remove(key);
            HttpContext.Current.Cache.Insert(key,data);
        }

        public T Retrieve<T>(string key)
        {
            if(HttpContext.Current.Cache[key]!=null)
                return (T)HttpContext.Current.Cache[key];
            return default(T);
        }
    }

如今再回頭看看,咱們解決了開始列舉的種種問題,使得代碼更加容易測試,更易讀,更易懂。

下面是Adapter(適配器)模式的UML圖示:

image

從圖中能夠看出,客戶有一個對抽象(Target)的引用。在這裏,該抽象就是ICacheStorage接口。Adapter是Target接口的一個實現,它只是將Operation方法委託給Adaptee類,這裏的Adapter類就是指咱們的HttpCacheStorage類,而Adaptee類則是指HttpContext.Current.Cache提供的具體操做方法。

具體的描述以下:

image

這樣,當咱們切換到Memcached,抑或是MS Velocity的時候,只須要建立一個Adapter,讓ProductService類與該緩存存儲提供者經過公共的ICacheStorage接口交互便可。

 

 

 

從這裏咱們知道:

Adapter模式很是簡單,它惟一的做用就是讓具備不兼容接口的類可以在一塊兒工做。

因爲Adapter模式並非惟一可以幫助處理緩存數據的模式,下面的章節將會研究Proxy設計模式如何來幫助解決緩存問題的。

 

在這裏,咱們還有最後一個問題沒有解決,就是在當前設計中,爲了使用ProductService類,老是不得不爲構造器提供ICacheStorage實現,可是若是不但願緩存數據呢? 一種作法是提供一個null引用,可是這意味着須要檢查空的ICacheStorage實現從而弄亂代碼,更好的方式則是使用NullObject模式來處理這種特殊狀況。

Null Object(空對象模式,有時也被稱爲特殊狀況模式)也是一種極爲簡單的模式。當不但願指定或不能指定某個類的有效實例並且不但願處處傳遞null引用時,這個模式就有用武之地。Null對象的做用是代替null引用並實現相同的接口可是沒有行爲

若是不但願ProductService類中緩存數據,Null Object模式能夠派上用場:

public class NullObjectCache:ICacheStorage
    {
        public void Remove(string key)
        {
        }

        public void Store(string key, object data)
        {
        }

        public T Retrieve<T>(string key)
        {
            return default(T);
        }
    }

這樣,當咱們請求緩存數據的時候,它什麼都不作並且老是向ProductService返回null值,確保不會緩存任何數據。

 

最後,總結一下:

三種設計模式分組。

依賴注入原則。

Adapter模式具體應用。

Null Object模式用於處理空對象。

相關文章
相關標籤/搜索