开闭原则------(转)
引言
本文是新開設(shè)的MSDN軟件設(shè)計(jì)基礎(chǔ)專欄的第一篇文章。我的目的是以不局限于某種特定工具或者某個(gè)(軟件工程)周期方法(lifecycle methodology)的方式來討論設(shè)計(jì)的模式和原則。換言之,我計(jì)劃討論一些可以引導(dǎo)你使用任何技術(shù),或者在任何項(xiàng)目中更好地進(jìn)行設(shè)計(jì)的基礎(chǔ)知識(shí)。
我喜歡以討論開閉原則和其他由 Robert C.Martin 在其著作《敏捷軟件開發(fā),原則,模式和實(shí)踐》中所倡導(dǎo)的相關(guān)主題作為開始。不要因?yàn)樵跇?biāo)題中出現(xiàn)“敏捷”一詞就把書合上了,因?yàn)檫@本書實(shí)際上完全是關(guān)于如何竭力進(jìn)行優(yōu)良軟件設(shè)計(jì)的。
問下你自己:有多少次你是從零開始去寫一個(gè)全新的應(yīng)用程序?又有多少次你是通過將新功能添加到現(xiàn)有代碼庫(codebase)中來作為開始?恐怕大多數(shù)的情況下,你是花費(fèi)了更多的時(shí)間將新功能添加到現(xiàn)有代碼庫中吧。
然后再問自己另一個(gè)問題:寫全新的代碼容易還是對(duì)現(xiàn)有代碼進(jìn)行修改容易?通常對(duì)我來說寫全新的方法和類要比深入舊代碼中,找出我想要修改的部分容易得多。修改舊有代碼增添了破壞已有功能的風(fēng)險(xiǎn)。對(duì)于新代碼來說,你通常只需要測試下新實(shí)現(xiàn)的功能就可以了。而當(dāng)你修改舊有代碼時(shí),你不得不既要測試你更改的部分,還要進(jìn)行一系列的兼容測試,以保證你沒有破壞任何的舊有代碼。
所以,你通常基于現(xiàn)有的代碼庫進(jìn)行工作,可是寫全新的代碼又比修改舊的代碼容易得多。你難道不想像寫全新代碼一樣多產(chǎn)、輕松地去對(duì)現(xiàn)有的代碼庫進(jìn)行擴(kuò)展么?這就是開閉原則一展身手的地方了。我來解釋一下開閉原則,它的意思是:軟件實(shí)體應(yīng)該對(duì)于擴(kuò)展是開放的,而對(duì)于修改是關(guān)閉的。
從字面上看這好像是矛盾的,實(shí)際并非如此。它的全部含義就是你應(yīng)該這樣去構(gòu)建一個(gè)應(yīng)用程序:可以在對(duì)現(xiàn)有代碼做最小修改的同時(shí)添加新的功能。我曾經(jīng)認(rèn)為開閉原則僅僅是意味著使用插件(plugins),但并不是這么簡單。
你應(yīng)該避免一個(gè)小小的改動(dòng)就波及了你應(yīng)用程序中的多個(gè)類。這樣會(huì)使程序更加脆弱,更傾向于產(chǎn)生向下兼容的問題,并使擴(kuò)展付出更高的代價(jià)。為了隔離變化,你會(huì)想要以一種一旦寫好了就再也不需要修改的方式去寫類和方法。
然而你如何構(gòu)建代碼以實(shí)現(xiàn)隔離變化呢?我想說的第一步就是遵循單一責(zé)任原則。
單一責(zé)任原則
在遵循開閉原則的過程中,我期望能夠?qū)懗鲆粋€(gè)類或者方法,在以后我回過頭讀它的時(shí)候,會(huì)很舒服地看到它能完成它的工作并且我也不需要再修改它。你永遠(yuǎn)也達(dá)不到真正的開閉天堂,但是通過嚴(yán)格地遵循與之相關(guān)的單一責(zé)任原則:一個(gè)類應(yīng)該有并且只有一個(gè)更改的理由,你可以非常靠近地接近它。
寫那些永遠(yuǎn)也不需要進(jìn)行修改的類的最簡單方法就是寫一些只能做一件事情的類。通過這種方式,一個(gè)類只有在它所確切負(fù)責(zé)的那件事更改時(shí)它才需要更改。代碼1演示了沒有遵循單一責(zé)任原則的一個(gè)例子。我真的懷疑你正在像這樣設(shè)計(jì)一個(gè)系統(tǒng),但是最好記得為什么我們不應(yīng)該這樣去構(gòu)建代碼。
代碼1. 這個(gè)類負(fù)責(zé)了太多的事
1 public class OrderProcessingModule { 2 public void Process(OrderStatusMessage orderStatusMessage) { 3 // 從配置文件中讀取連接字符串 4 string connectionString = 5 ConfigurationManager.ConnectionStrings["Main"].ConnectionString; 6 7 Order order = null; 8 9 using (SqlConnection connection = 10 new SqlConnection(connectionString)) { 11 // 從數(shù)據(jù)庫中獲取一些數(shù)據(jù) 12 order = fetchData(orderStatusMessage, connection); 13 } 14 15 // 向來自于OrderStatusMessage的訂單提交變更 16 updateTheOrder(order); 17 18 // 國際訂單有一些特定的規(guī)則 19 if (order.IsInternational) { 20 processInternationalOrder(order); 21 } 22 23 // 對(duì)于大批量訂單我們需要特別處理 24 else if (order.LineItems.Count > 10) { 25 processLargeDomesticOrder(order); 26 } 27 // 小的國內(nèi)訂單也需要區(qū)別處理 28 else { 29 processRegularDomesticOrder(order); 30 } 31 32 // 如果訂單準(zhǔn)備好了就發(fā)貨 33 if (order.IsReadyToShip()) { 34 ShippingGateway gateway = new ShippingGateway(); 35 36 // 將訂單對(duì)象提交運(yùn)送 37 ShipmentMessage message = createShipmentMessageForOrder(order); 38 gateway.SendShipment(message); 39 } 40 } View CodeOrderProcessingModule真是太忙了。它要進(jìn)行數(shù)據(jù)訪問、獲取配置文件信息、為訂單處理執(zhí)行業(yè)務(wù)規(guī)則(可能本身就非常復(fù)雜),并且將完成的訂單轉(zhuǎn)移出貨。通常的情況是,如果你通過這種方式創(chuàng)建了OrderProcessingModule,你將會(huì)經(jīng)常深入到這段代碼中進(jìn)行修改。而許多系統(tǒng)需求的變化也會(huì)造成OrderProcessingModule的代碼產(chǎn)生非常多的變更,讓系統(tǒng)變得岌岌可危并使變更花費(fèi)很大代價(jià)。
除了這種一大塊代碼的方式,你應(yīng)該遵循單一責(zé)任原則,將整個(gè)OrderProcessingModule分成一系列相關(guān)類的子系統(tǒng),每一個(gè)類完成它自己特定的職責(zé)。舉個(gè)例子,你可以將所有數(shù)據(jù)訪問的功能放到一個(gè)新類中,管它叫OrderDataService,而把Order的業(yè)務(wù)邏輯放到另一個(gè)類中(我會(huì)在下一節(jié)進(jìn)行更詳細(xì)的講述)。
根據(jù)開閉原則,通過將業(yè)務(wù)邏輯和數(shù)據(jù)訪問的職責(zé)劃分到不同的類中,你將可以獨(dú)立地改變它們中的一個(gè)而不會(huì)影響到另一個(gè)。數(shù)據(jù)庫物理部署的變化可能將使你把數(shù)據(jù)訪問部分完全更換掉(對(duì)擴(kuò)展開放),然而訂單邏輯類依然沒有任何改動(dòng)(對(duì)變更關(guān)閉)。
單一責(zé)任原則的要點(diǎn)不僅僅是寫一些更小的類和方法。它的要點(diǎn)是每一個(gè)類應(yīng)該實(shí)現(xiàn)一系列緊密相關(guān)的功能。遵循單一責(zé)任原則的最簡單辦法就是不斷地問自己是不是這個(gè)類的每一個(gè)方法和操作都與這個(gè)類的名稱直接相關(guān)。如果你找到了一些方法與這個(gè)類的名稱不相稱,你可以考慮將這些方法移到另一個(gè)類中。
責(zé)任鏈模式
業(yè)務(wù)規(guī)則在代碼庫(Codebase)的生命周期中相對(duì)于系統(tǒng)的任何其他部分可能面臨更多的變化。在OrderProcessingModule類中,基于接收的訂單的類型,對(duì)于訂單的處理有不少的分支邏輯:
1 if (order.IsInternational) { 2 processInternationalOrder(order); 3 }else if (order.LineItems.Count > 10) { 4 processLargeDomesticOrder(order); 5 }else { 6 processRegularDomesticOrder(order); 7 } View Code一個(gè)真正的訂單處理系統(tǒng)很有可能在業(yè)務(wù)增長的時(shí)候包含更多類型的訂單 -- 并且要考慮很多的特殊情況,比如對(duì)于政府或者受到優(yōu)待的客戶,以及每周一次的特別供應(yīng)。對(duì)你而言,如果能夠書寫并且測試一些新的訂單處理邏輯而不用冒著破壞現(xiàn)有業(yè)務(wù)規(guī)則的風(fēng)險(xiǎn)將會(huì)是一件非常有利的事情。
最后,通過代碼2所示的責(zé)任鏈模式,對(duì)于這個(gè)訂單處理的例子你可以更進(jìn)一步地運(yùn)用開閉原則。我所做的第一件事就是把所有的分支判斷由OrderProcessingModule中轉(zhuǎn)移到一個(gè)獨(dú)立的類中,這個(gè)類實(shí)現(xiàn)IOrderHandler接口:
1 public interface IOrderHandler { 2 void ProcessOrder(Order order); 3 bool CanProcess(Order order); 4 } View Code代碼2. 引入責(zé)任鏈
1 public class OrderProcessingModule { 2 private IOrderHandler[] _handlers; 3 4 public OrderProcessingModule() { 5 _handlers = new IOrderHandler[] { 6 new InternationalOrderHandler(), 7 new SmallDomesticOrderHandler(), 8 new LargeDomesticOrderHandler(), 9 }; 10 } 11 12 public void Process (OrderStatusMessage orderStatusMessage, 13 Order order) { 14 // 對(duì)來自O(shè)rderStatusMessage的訂單提交變更 15 updateTheOrder(order); 16 17 // 找出知道如何處理這個(gè)訂單的第一個(gè)IOrderHandler 18 IOrderHandler handler = 19 Array.Find(_handlers, h => h.CanProcess(order)); 20 21 handler.ProcessOrder(order); 22 } 23 24 private void updateTheOrder(Order order) { 25 } 26 } View Code然后我可以對(duì)于每種類型的訂單寫一個(gè)獨(dú)立的IOrderHandler實(shí)現(xiàn),包含著像這樣的基本邏輯,“我知道如何處理這個(gè)訂單,讓我來處理它”。
現(xiàn)在對(duì)于每種類型的訂單處理邏輯都分隔到了獨(dú)立的處理類中(Handler Class),對(duì)于某種類型的訂單你可以更改業(yè)務(wù)規(guī)則而不用擔(dān)心會(huì)破化其他類型訂單的規(guī)則。更好的是,你可以添加全新類型的訂單處理程序而只需要對(duì)現(xiàn)有代碼做細(xì)小的改動(dòng)。
舉個(gè)例子,比如說,以后某個(gè)時(shí)候,我需要在系統(tǒng)中為政府的訂單添加支持。通過責(zé)任鏈模式,我可以添加一個(gè)全新的類,叫做GovernmentOrderHandler,這個(gè)類實(shí)現(xiàn)IOrderHandler接口。一旦我對(duì)GovernmentOrderHanlder按期望的方式所進(jìn)行的工作感到滿意,通過修改OrderProcessingModule類構(gòu)造函數(shù)的一行代碼,我就可以添加這個(gè)新的政府訂單處理規(guī)則:
1 public OrderProcessingModule() { 2 _handlers = new IOrderHandler[] { 3 new InternationalOrderHandler(), 4 new SmallDomesticOrderHandler(), 5 new LargeDomesticOrderHandler(), 6 new GovernmentOrderHandler(), // 新添加的處理規(guī)則 7 }; 8 } View Code通過在訂單處理規(guī)則上遵循開閉原則,我使得在系統(tǒng)中添加新類型的訂單處理邏輯容易得多。我能夠用比在一個(gè)類中實(shí)現(xiàn)各種類型訂單處理所要面臨的小得多的影響其它類型訂單的風(fēng)險(xiǎn)來完成政府訂單規(guī)則的添加。
雙重分發(fā)
如果以后上面的步驟變得更加復(fù)雜該怎么辦呢?如果僅僅依靠多態(tài)無法滿足未來可能出現(xiàn)的所有變化呢?我們可以使用稱為雙重分發(fā)的模式將變化推入子類中,通過這種方式,我們不需要破壞現(xiàn)有的接口定義。
舉個(gè)例子,比如說我們正在構(gòu)建一個(gè)復(fù)雜的桌面應(yīng)用程序,它能一次顯示某種主面板中的一屏(screen)。每次我在程序中打開一個(gè)新屏,我需要做很多的事情。我可能需要更改可用的菜單,檢查那些已經(jīng)打開的屏幕的狀態(tài),做一些定制整個(gè)屏幕顯示的事,并且,yeah,以某種方式顯示新屏。
典型地,我會(huì)使用某種Model View Presenter(MVP)模式的變體作為我的桌面應(yīng)用程序的構(gòu)架,并且我通常會(huì)使用程序控制器(Application Controller)模式去協(xié)調(diào)應(yīng)用程序中各種不同MVP組(譯注:因?yàn)镸VP由三個(gè)部分組成,所以將每三個(gè)部件分為一組)。通過在MVP中使用一個(gè)程序控制器(了解MVP的更多信息,可以參考Jean-Paul Boodhoo在MSDN雜志設(shè)計(jì)模式專欄中關(guān)于MVP模式的文章,http://msdn.microsoft.com/en-us/magazine/cc188690.aspx ),激活屏幕可能會(huì)包含下面三個(gè)基本的部分:
如果我所需要做得只不過簡單地在激活時(shí)顯示ApplicationShell中的視圖,代碼可能如同代碼3所示。對(duì)于簡單的應(yīng)用程序來說這完全是可行的,但是如果程序變得更加復(fù)雜會(huì)怎樣呢?如果在下一個(gè)發(fā)布版本中,我有新的需求,在某些屏幕激活的時(shí)候向主Shell中添加菜單項(xiàng)?如果對(duì)于某些而非全部的視圖,我想要在靠著主屏幕左邊際的新面板中顯示額外的控件?
代碼3.一個(gè)簡單的基于視圖的應(yīng)用程序
1 public interface IApplicationShell { 2 void DisplayMainView(object view); 3 } 4 5 public interface IPresenter { 6 // 僅僅提供對(duì)于內(nèi)部Windows窗體用戶控件或者窗體的訪問 7 object View { get; } 8 } 9 10 public class ApplicationController { 11 private IApplicationShell _shell; 12 13 public ApplicationController(IApplicationShell shell) { 14 _shell = shell; 15 } 16 17 public void ActivateScreen(IPresenter presenter) { 18 teardownCurrentScreen(); 19 20 // 設(shè)置新屏幕 21 _shell.DisplayMainView(presenter.View); 22 } 23 24 private void teardownCurrentScreen() { 25 // 移除現(xiàn)存屏幕 26 } 27 } View Code我還想讓構(gòu)架支持嵌入(pluggable),以便于通過簡單的嵌入新的提供器就可以在程序中添加新屏幕,所以現(xiàn)有提供器的抽象應(yīng)該對(duì)于這些新菜單以及左邊面板的構(gòu)造函數(shù)有所了解。然后我還必須更改ApplicationShell或者程序控制器,以對(duì)新菜單項(xiàng)以及左邊面板中額外的控件做出響應(yīng)。
代碼4 顯示了一種可能的解決方案。我向IPrensenter接口中添加了新的屬性用于對(duì)新的菜單項(xiàng)以及任何有可能添加到新的左側(cè)面板中的控件進(jìn)行建模。我同樣為這些新的概念向IApplicationShell添加了一些新的成員。然后我在ApplicationController.ActivateScreen(IPresenter)方法中添加了些新代碼
代碼4. 試圖擴(kuò)展IPresenter
1 public class MenuCommand{ 2 // ... 3 } 4 public interface IApplicationShell{ 5 void DisplayMainView(object view); 6 7 // 新行為 8 void AddMenuCommands(MenuCommand[] commands); 9 void DisplayInExplorerPane(object paneView); 10 } 11 public interface IPresenter 12 { 13 object View { get; } 14 15 // 新屬性 16 MenuCommand[] Commands{ get; } 17 object[] ExplorerViews { get; } 18 } 19 public class ApplicationController { 20 private IApplicationShell _shell; 21 22 public ApplicationController(IApplicationShell shell){ 23 _shell = shell; 24 } 25 26 public void ActivateScreen(IPresenter presenter) 27 { 28 teardownCurrentScreen(); 29 30 // 設(shè)置新屏幕 31 _shell.DisplayMainView(presenter.View); 32 33 // 新代碼 34 _shell.AddMenuCommands(presenter.Commands); 35 foreach (var explorerView in presenter.ExplorerViews){ 36 _shell.DisplayInExplorerPane(explorerView); 37 } 38 } 39 40 private void teardownCurrentScreen() 41 { 42 // 移除現(xiàn)有屏幕 43 } 44 } View Code那么,這個(gè)解決方案遵守了開閉原則么?一點(diǎn)也沒有。首先,我必須修改IPresenter接口。因?yàn)樗且粋€(gè)接口,我必須在代碼庫中修改IPresenter接口的每一個(gè)實(shí)現(xiàn),并且為這些新的方法添加一些空的實(shí)現(xiàn),僅僅為了我的代碼可以再一次編譯通過。這通常是一個(gè)無法忍受的改變,尤其是當(dāng)你不能直接控制這些IPresenter實(shí)現(xiàn)中的任何一個(gè)的時(shí)候。關(guān)于這部分我們后面再說。
我同樣需要修改ApplicationController類,以使得它知道主ApplicationShell中的屏幕所可能需要的所有新的定制化類型。最后,我需要修改ApplicationShell以使它支持這些新的Shell定制。變化很小,但是同樣,我很有可能不久以后想要再次添加更多的屏幕定制。
在一個(gè)真正的應(yīng)用程序中,ApplicationControll類可能會(huì)變得足夠復(fù)雜,而不必承擔(dān)額外配置ApplicationShell的責(zé)任。我們將這些職責(zé)置于每個(gè)提供器中可能會(huì)更好一些。
通過使用一個(gè)名為Presenter的抽象類,而不是使用一個(gè)接口將會(huì)減少修改每個(gè)IPresenter接口的實(shí)現(xiàn)的痛苦。像代碼5這樣,我可以僅僅向抽象類中添加一些默認(rèn)的實(shí)現(xiàn)。并且在添加新的行為時(shí)我不需要修改任何現(xiàn)有的Presenter實(shí)現(xiàn)。
代碼5.使用抽象的Presenter
1 public abstract class BasePresenter 2 { 3 public abstract object View { get;} 4 5 // Commands 的默認(rèn)實(shí)現(xiàn) 6 public virtual MenuCommand[] Commands { 7 get{ 8 return new MenuCommand[0]; 9 } 10 } 11 12 // 默認(rèn)的 ExplorerViews 13 public virtual object[] ExplorerViews{ 14 get{ 15 return new object[0]; 16 } 17 } 18 } View Code最后,還有一種更靠近開閉原則的方式需要說明。除了在IPresenter和BasePresenter中添加Get選擇器,我可以使用雙重分發(fā)模式。
幾天前在實(shí)際生活中我意外地得到了雙重分發(fā)模式的一個(gè)演示。我的團(tuán)隊(duì)剛剛轉(zhuǎn)移到一個(gè)新的辦公室中,我們一直在解決網(wǎng)絡(luò)上的問題。我們的網(wǎng)絡(luò)負(fù)責(zé)人上周給我打了個(gè)電話并且告訴我我的同事應(yīng)該如何做以連接到VPN。他喋喋不休地向我講述一大堆我不懂的網(wǎng)絡(luò)術(shù)語,所以我最終把電話給了我的同事,讓他們直接對(duì)話。
現(xiàn)在我們也為程序控制器做同樣的事情。并非讓程序控制器去詢問每個(gè)提供器哪些需要被顯示在ApplicationShell中,提供器可以簡單地忽略中間人并且告訴ApplicationShell對(duì)于每一屏應(yīng)該顯示些什么(查看 代碼6)。
1 public interface IPresenter { 2 void SetupView(IApplicationShell shell); 3 } 4 5 public class ApplicationController { 6 private IApplicationShell _shell; 7 8 public ApplicationController(IApplicationShell shell) { 9 _shell = shell; 10 } 11 12 public void ActivateScreen(IPresenter presenter) { 13 teardownCurrentScreen(); 14 15 // 使用雙重分發(fā)設(shè)置新屏幕 16 presenter.SetupView(_shell); 17 } 18 19 private void teardownCurrentScreen() { 20 // 移除現(xiàn)有屏幕 21 } 22 } View Code起初不管我如何做,我都將不得不為了新的定制菜單以及左欄面板中的控件而去修改ApplicationShell,但如果我使用雙重分發(fā)策略,對(duì)于新的變更,程序控制器和提供器都只需要做非常少的修改。創(chuàng)建額外的屏幕概念(screen concepts)我不再需要修改程序控制器和提供器類。對(duì)于新的Shell概念(screen concepts),這個(gè)構(gòu)架是開放的可擴(kuò)展的,而程序控制器和單獨(dú)的提供器類對(duì)于修改是關(guān)閉的。
Liskov 替換原則
如果我前面所說的,使用開閉原則最通常的做法就是使用多態(tài)去用一個(gè)全新的類替換程序中現(xiàn)存的一部分。就拿最早的例子來說,你有一個(gè)稱為BusinessProcess的類,它的工作是,嗯,執(zhí)行業(yè)務(wù)處理。在這個(gè)過程中,它需要從數(shù)據(jù)源中訪問數(shù)據(jù):
1 public class BusinessProcess { 2 private IDataSource _source; 3 4 public BusinessProcess(IDataSource source) { 5 _source = source; 6 } 7 } 8 public interface IDataSource { 9 Entity FindEntity(long key); 10 } View Code如果你可以通過實(shí)現(xiàn)IDataSource對(duì)這個(gè)系統(tǒng)進(jìn)行擴(kuò)展并且不對(duì)BusinessProcess類做任何的修改,那么這個(gè)設(shè)計(jì)就遵循了開閉原則。你可能起初通過一個(gè)簡單的基于XML文件的機(jī)制,然后轉(zhuǎn)而使用數(shù)據(jù)庫進(jìn)行存儲(chǔ),隨后添加某種類型的緩存-- 但是你還是不想修改BusinessProcess類。所有這些都是可能的,只要你能夠遵循一個(gè)相關(guān)的原則:Liskov替代原則。
粗略地說,如果你可以在任何接受抽象的地方使用那個(gè)抽象的任何實(shí)現(xiàn),就是在遵循Liskov替換原則。BusinessProcess應(yīng)該可以使用IDataSource的任何實(shí)現(xiàn)而不需要進(jìn)行修改。BusinessProcess不應(yīng)該知道IDataSource中除了進(jìn)行通信的的公共接口以外的任何內(nèi)部事務(wù)。
為了深入這個(gè)觀點(diǎn),代碼7演示了一個(gè)沒有遵循Liskov替換原則的例子。這個(gè)版本的BusinessProcess類型對(duì)于獲取FileSource有著特定的邏輯,同時(shí)依賴一些針對(duì)于DatabaseSource類的特定錯(cuò)誤處理邏輯。你應(yīng)該創(chuàng)建IDataSource的實(shí)現(xiàn)以便他們可以處理所有特定的底層需求。通過這樣做可以使 BusinessProcess類像代碼8這樣書寫:
代碼7.沒有對(duì)IDataSource進(jìn)行抽象的BusinessProcess類
1 public class BusinessProcess { 2 private IDataSource _source; 3 4 public BusinessProcess(IDataSource source) { 5 _source = source; 6 } 7 8 public void Process() { 9 long theKey = 112; 10 11 // 針對(duì)于 FileSource的特定代碼 12 if (_source is FileSource) { 13 ((FileSource)_source).LoadFile(); 14 } 15 16 try { 17 Entity entity = _source.FindEntity(theKey); 18 } 19 catch (System.Data.DataException) { 20 // 對(duì)于DatabaseSource的特定處理程序 21 // 這是 向下轉(zhuǎn)換(downcast) 的一個(gè)例子 22 ((DatabaseSource)_source).CleanUpTheConnection(); 23 } 24 } 25 } View Code代碼8更好的BusinessProcess
1 public class BusinessProcess { 2 private readonly IDataSource _source; 3 4 public BusinessProcess(IDataSource source) { 5 _source = source; 6 } 7 8 public void Process(Message message) { 9 // Process()方法的第一部分 10 11 // 這里不再有針對(duì)于某個(gè)特定IDataSource實(shí)現(xiàn)的代碼 12 Entity entity = _source.FindEntity(message.Key); 13 14 // Process()方法的最后部分 15 } 16 } View Code尋找閉包
記得,如果一個(gè)類僅僅依賴于它所交互的另一個(gè)類的公共契約(Contract)(譯注:其實(shí)就是公共接口),開閉原則只是通過多態(tài)來實(shí)現(xiàn)。如果在某一部分中,一個(gè)抽象了的類必須向下轉(zhuǎn)換為特定的子類,那么你就沒有遵循開閉原則。
如果一個(gè)使用另一個(gè)類的類嵌入了關(guān)于它所依賴的類的內(nèi)部工作(比如假設(shè)一個(gè)方法的返回值總是由大到小排序),那么實(shí)際上對(duì)于這個(gè)依賴你不能替換為另一個(gè)實(shí)現(xiàn)。因?yàn)閷?duì)于閱讀你代碼的人來說它們是不明顯的,這種類型的對(duì)于特定實(shí)現(xiàn)的隱式耦合特別有害。不要讓抽象的消費(fèi)者依賴于除過那個(gè)抽象的公共契約的任何東西。
我建議你將開閉原則作為一個(gè)設(shè)計(jì)方向而非一個(gè)完全的目標(biāo)。如果你試圖將你能想到所有可能改變的東西都變成完全可嵌入式的,你很有可能創(chuàng)建一個(gè)非常難于工作的過度設(shè)計(jì)的系統(tǒng)。你可能并非總是試圖寫一些在各個(gè)方面都滿足開閉原則的代碼,但是即使只進(jìn)行到中途也是非常有益的。
轉(zhuǎn)載于:https://www.cnblogs.com/shao-shao/articles/3450759.html
總結(jié)
以上是生活随笔為你收集整理的开闭原则------(转)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Java回顾之多线程同步
- 下一篇: js中文正则