Asp.net设计模式笔记之一:理解设计模式
http://www.cnblogs.com/scy251147/p/3793547.html
GOF設(shè)計模式著作中的23種設(shè)計模式可以分成三組:創(chuàng)建型(Creational),結(jié)構(gòu)型(Structural),行為型(Behavioral)。下面來做詳細的剖析。
創(chuàng)建型
創(chuàng)建型模式處理對象構(gòu)造和引用。他們將對象實例的實例化責任從客戶代碼中抽象出來,從而讓代碼保持松散耦合,將創(chuàng)建復雜對象的責任放在一個地方,這遵循了單一責任原則和分離關(guān)注點原則。
下面是“創(chuàng)建型”分組中的模式:
1.Abstract Factory(抽象工廠)模式:提供一個接口來創(chuàng)建一組相關(guān)的對象。
2.Factory Method(工廠方法)模式:支持使用一個類來委托創(chuàng)建有效對象的責任。
3.Builder(生成器)模式:將對象本身的構(gòu)造分離出來,從而能夠構(gòu)造對象的不同版本。
4.Prototype(原型)模式:能夠從一個原型實例來復制或克隆類,而不是創(chuàng)建新實例。
5.Singleton(單例)模式:支持一個類只實例化一次,并只有一個可用來訪問它的全局訪問點。
結(jié)構(gòu)型
結(jié)構(gòu)型模式處理對象的組合與關(guān)系,以滿足大型系統(tǒng)的需要。
下面是“結(jié)構(gòu)型”分組中的模式:
1.Adapter(適配器)模式:使不兼容接口的類能夠一起使用。
2.Bridge(橋接)模式:將抽象與其實現(xiàn)分離,允許實現(xiàn)和抽象彼此獨立地改變。
3.Composite(組合)模式:可以像對待對象的單個實例那樣來對待一組表示層次結(jié)構(gòu)的對象。
4.Decorator(裝飾)模式:能夠動態(tài)包裝一個類并擴展其行為。
5.Facade(門面)模式:提供一個簡單的接口并控制對一組復雜接口和子系統(tǒng)的訪問。
6.Flyweight(享元)模式:提供一種在許多小類之間高效共享數(shù)據(jù)的方式。
7.Proxy(代理)模式:為一個實例化成本很高的更復雜的類提供一個占位符。
行為型
行為型模式處理對象之間在責任和算法方面的通信。這個分組中的模式將復雜行為封裝起來并將其從系統(tǒng)控制流中抽象出來,這樣就使復雜系統(tǒng)更容易理解和維護。
下面是”行為型“分組中的模式:
1.Chain Of Responsibility(責任鏈)模式:允許將命令動態(tài)鏈接起來處理請求。
2.Command(命令)模式:將一個方法封裝成一個對象,并將該命令的執(zhí)行與它的調(diào)用者分離。
3.Interpreter(解釋器)模式:指定如何執(zhí)行某種語言中的語句。
4.Iterator(迭代器)模式:提供以形式化的方式來導航集合的方法。
5.Mediator(中介者)模式:定義一個對象,可以讓其他兩個對象進行通信而不必讓它們知道彼此。
6.Memento(備忘錄)模式:允許將對象恢復到以前的狀態(tài)。
7.Observer(觀察者)模式:定義一個或多個類在另一個類發(fā)生變化時接到報警。
8.State(狀態(tài))模式:允許對象通過委托給獨立的,可改變的狀態(tài)對象來改變自己的行為。
9.Strategy(策略)模式:能夠?qū)⑺惴ǚ庋b到一個類中并在運行時轉(zhuǎn)換,以改變對象的行為。
10.Template Method(模板方法)模式:定義算法流程控制,但允許子類重寫或?qū)崿F(xiàn)執(zhí)行步驟。
11.Vistor(訪問者)模式:能夠在類上執(zhí)行新的功能而不影響類的結(jié)構(gòu)。
上面介紹了眾多的設(shè)計模式及其分組。但是如何來選擇和運用呢?下面有一些需要注意的事項:
1.在不了解模式的情況下不能運用他們。
2.在設(shè)計的時候,要衡量是否有必要引入設(shè)計模式的復雜性。最好能衡量下實現(xiàn)某種模式所需的時間與該模式能夠帶來的效益。謹記KISS原則:保持簡單淺顯。
3.將問題泛化,以更抽象的方式識別正在處理的問題。設(shè)計模式是高層次的解決方案,試著把問題抽象,而且不要過于關(guān)注具體問題的細節(jié)。
4.了解具有類似性質(zhì)的模式以及同組中的其他模式。以前已經(jīng)使用過某個模式并不意味著在解決問題時它總是正確的模式選擇。
5.封裝變化的部分。了解應用程序中什么可能發(fā)生變化。如果知道某個特殊的報價折扣算法將隨時間發(fā)生變化,那么尋找一種模式來幫助您在不影響應用程序其余部分的情況下改變該算法。
6.在選擇好設(shè)計模式之后,確保在命名解決方案中的參與者時使用該模式的語言及領(lǐng)域語言。例如,如果正在使用策略模式為不同的快遞公司計價提供解決方案,那么相應地為他們明明,如FedExShippingCostStrategy。通過組合使用模式的公共詞匯表和領(lǐng)域語言,會讓代碼更具可讀性,而且更能夠讓其他具備模式知識的開發(fā)者理解。
?
就設(shè)計模式而言,除了學習之外沒有其他替代方法。對每種設(shè)計模式了解得越多,在運用他們時就會準備的更好。當遇到一個問題正在尋找解決方案時,掃描一下每種模式的目的,喚起自己的記憶。
一種很好地學習方法就是試著識別.net框架中的模式,比如:Asp.net Cache使用了Singleton模式,在創(chuàng)建新的Guid實例時使用了Factory Method模式,.Net 2 xml類使用Factory Method模式,而1.0版并沒有使用。
?
下面我們以一個快速模式示例來進行講解,以便于加深映像。
新建一個類庫項目0617.DaemonPattern.Service,然后引用System.web程序集。
首先添加一個Product.cs的空類作為我們的Model:
?| 1 2 3 4 | public class Product ?{ ?} |
然后添加ProductRepository.cs類作為我們的數(shù)據(jù)存儲倉庫,從這里我們可以從數(shù)據(jù)庫獲取數(shù)據(jù)實體對象:
?| 1 2 3 4 5 6 7 8 9 | public class ProductRepository { ????public IList<Product> GetAllProductsIn(int categoryId) ????{ ????????var products = new List<Product>(); ????????//Database operation to populate products. ????????return products; ????} } |
最后添加一個名稱為ProductService.cs的類,代碼如下:
?| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | 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倉庫從數(shù)據(jù)庫獲取數(shù)據(jù)。
這個類庫帶來的問題有以下幾點:
1.ProductService依賴于ProductRepository類。如果ProductRepository類中的API發(fā)生改變,就需要在ProductService類中進行修改。
2.代碼不可測試。如果不讓真正的ProductRepository類連接到真正的數(shù)據(jù)庫,就不能測試ProductService的方法,因為這兩個類之間存在著緊密耦合。另一個與測試有關(guān)的問題是,該代碼依賴于使用Http上下文來緩存商品。很難測試這種與Http上下文緊密耦合的代碼。
3.被迫使用Http上下文來緩存。在當前狀態(tài),若使用Velocity或Memcached之類的緩存存儲提供者,則需要修改ProductService類以及所有其他使用緩存的類。Verlocity和Memcached都是分布式內(nèi)存對象緩存系統(tǒng),可以用來替代Asp.net的默認緩存機制。
隨意,綜上看來,代碼耦合度過高,不易進行測試,同時也不易進行替換。
?
既然知道了存在的問題,那么就讓我們來對其進行重構(gòu)。
首先,考慮到ProductService類依賴于ProductRepository類的問題。在當前狀態(tài)中,ProductService類非常脆弱,如果ProductRepository類的API改變,就需要修改ProductService類。這破壞了分離關(guān)注點和單一職責原則。
1.依賴倒置原則(依賴抽象而不要依賴具體)
可以通過依賴倒置原則來解耦ProductService類和ProductRepository類,讓它們都依賴于抽象:接口。
在ProductRepository類上面右擊,選擇“重構(gòu)”->“提取接口”選項,會自動給我們生成一個IProductRepository.cs類:
?| 1 2 3 4 | public interface IProductRepository ????{ ????????IList<Product> GetAllProductsIn(int categoryId); ????} |
修改現(xiàn)有的ProductRepository類,以實現(xiàn)新創(chuàng)建的接口,代碼如下:
?| 1 2 3 4 5 6 7 8 9 | public class ProductRepository : IProductRepository ???{ ???????public IList<Product> GetAllProductsIn(int categoryId) ???????{ ???????????var products = new List<Product>(); ???????????//Database operation to populate products. ???????????return products; ???????} ???} |
?
之后更新ProductService類,以確保它引用的是接口而非具體:
?| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | 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類現(xiàn)在只依賴于抽象而不是具體的實現(xiàn),這意味著ProductService類完全不知道任何實現(xiàn),從而確保它不是那么容易的被破壞掉,而且代碼在整體上說來對變化更有彈性。
但是,這里還有個問題,既是ProductService類仍然負責創(chuàng)建具體的實現(xiàn)。而且目前在沒有有效的ProductRepository類的情況下不可能測試代碼。所以這里我們需要引入另一個設(shè)計原則來解決這個問題:依賴注入原則。
由于ProductService類仍然與ProductRepository的具體實現(xiàn)綁定在了一起,通過依賴注入原則,我們可以將這一過程移到外部進行,具體方法就是通過該類的構(gòu)造器將其注入:
?| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | 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類遵循單一職責原則:它現(xiàn)在只關(guān)心如何協(xié)調(diào)從緩存或資源庫中檢索數(shù)據(jù),而不是創(chuàng)建具體的IProductRepository實現(xiàn)。
依賴注入有三種形式:構(gòu)造器,方法以及屬性。我們這里只是使用了構(gòu)造器注入。
當然,現(xiàn)在的代碼看上去基本沒問題了,但是一旦替換緩存機制的話,將會是一個比較棘手的問題,因為基于Http上下文的緩存沒有被封裝,替換其需要對當前類進行修改。這破壞了開放封閉原則:對擴展開放,對修改關(guān)閉。
由于Adapter(適配器)模式主要用來將一個類的某個轉(zhuǎn)換成一個兼容的接口,所以在當前的例子中,我們可以將HttpContext緩存API修改成想要使用的兼容API。然后可以使用依賴注入原則,通過一個接口將緩存API注入到ProductService類。
這里我們創(chuàng)建一個名為ICacheStorage的新街口,它包含有如下契約:
?| 1 2 3 4 5 6 | public interface ICacheStorage ???{ ???????void Remove(string key); ???????void Store(string key, object data); ???????T Retrieve<T>(string key); ???} |
在ProductService類中,我們就可以將其取代基于HttpContext的緩存實例:
?| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | 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來實現(xiàn):
?| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | 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); ????????} ????} |
現(xiàn)在再回頭看看,我們解決了開始列舉的種種問題,使得代碼更加容易測試,更易讀,更易懂。
下面是Adapter(適配器)模式的UML圖示:
從圖中可以看出,客戶有一個對抽象(Target)的引用。在這里,該抽象就是ICacheStorage接口。Adapter是Target接口的一個實現(xiàn),它只是將Operation方法委托給Adaptee類,這里的Adapter類就是指我們的HttpCacheStorage類,而Adaptee類則是指HttpContext.Current.Cache提供的具體操作方法。
具體的描述如下:
這樣,當我們切換到Memcached,抑或是MS Velocity的時候,只需要創(chuàng)建一個Adapter,讓ProductService類與該緩存存儲提供者通過公共的ICacheStorage接口交互即可。
?
?
?
從這里我們知道:
Adapter模式非常簡單,它唯一的作用就是讓具有不兼容接口的類能夠在一起工作。
由于Adapter模式并不是唯一能夠幫助處理緩存數(shù)據(jù)的模式,下面的章節(jié)將會研究Proxy設(shè)計模式如何來幫助解決緩存問題的。
?
在這里,我們還有最后一個問題沒有解決,就是在當前設(shè)計中,為了使用ProductService類,總是不得不為構(gòu)造器提供ICacheStorage實現(xiàn),但是如果不希望緩存數(shù)據(jù)呢? 一種做法是提供一個null引用,但是這意味著需要檢查空的ICacheStorage實現(xiàn)從而弄亂代碼,更好的方式則是使用NullObject模式來處理這種特殊情況。
Null Object(空對象模式,有時也被稱為特殊情況模式)也是一種極為簡單的模式。當不希望指定或不能指定某個類的有效實例而且不希望到處傳遞null引用時,這個模式就有用武之地。Null對象的作用是代替null引用并實現(xiàn)相同的接口但是沒有行為。
如果不希望ProductService類中緩存數(shù)據(jù),Null Object模式可以派上用場:
?| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | 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); ????????} ????} |
這樣,當我們請求緩存數(shù)據(jù)的時候,它什么都不做而且總是向ProductService返回null值,確保不會緩存任何數(shù)據(jù)。
?
最后,總結(jié)一下:
三種設(shè)計模式分組。
依賴注入原則。
Adapter模式具體應用。
Null Object模式用于處理空對象。
轉(zhuǎn)載于:https://www.cnblogs.com/aaa6818162/p/3963704.html
總結(jié)
以上是生活随笔為你收集整理的Asp.net设计模式笔记之一:理解设计模式的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Ubuntu 误改/etc/sudoer
- 下一篇: javascript设计模式之观察者模式