6.4 如何使用開閉原則
開閉原則是一個非常虛的原則,前面5個原則是對開閉原則的具體解釋,但是開閉原則并不局限于這么多,它“虛”得沒有邊界,就像“好好學習,天天向上”的口號一樣,告訴我們要好好學習,但是學什么,怎么學并沒有告訴我們,需要去體會和掌握,開閉原則也是一個口號,那我們怎么把這個口號應用到實際工作中呢?
1. 抽象約束
抽象是對一組事物的通用描述,沒有具體的實現,也就表示它可以有非常多的可能性,可以跟隨需求的變化而變化。因此,通過接口或抽象類可以約束一組可能變化的行為,并且能夠實現對擴展開放,其包含三層含義:第一,通過接口或抽象類約束擴展,對擴展進行邊界限定,不允許出現在接口或抽象類中不存在的public方法;第二,參數類型、引用對象盡量使用接口或者抽象類,而不是實現類;第三,抽象層盡量保持穩定,一旦確定即不允許修改。還是以書店為例,目前只是銷售小說類書籍,單一經營畢竟是有風險的,于是書店新增加了計算機書籍,它不僅包含書籍名稱、作者、價格等信息,還有一個獨特的屬性:面向的是什么領域,也就是它的范圍,比如是和編程語言相關的,還是和數據庫相關的,等等,修改后的類圖如圖6-3所示。

圖6-3 增加業務品種后的書店售書類圖
增加了一個接口IComputerBook和實現類Computer- Book,而BookStore不用做任何修改就可以完成書店銷售計算機書籍的業務。計算機書籍接口如代碼清單6-8所示。
代碼清單6-8 計算機書籍接口
public?interface?IComputerBook?extends?IBook{??????
?????//計算機書籍是有一個范圍
?????public?String?getScope();
}
很簡單,計算機書籍增加了一個方法,就是獲得該書籍的范圍,同時繼承IBook接口,畢竟計算機書籍也是書籍,其實現如代碼清單6-9所示。
代碼清單6-9 計算機書籍類
public?class?ComputerBook?implements?IComputerBook?{
?????private?String?name;
?????private?String?scope;
?????private?String?author;
?????private?int?price;?
?????public?ComputerBook(String?_name,int?_price,String?_author,String?_scope){
?????????????this.name=_name;
?????????????this.price?=?_price;
?????????????this.author?=?_author;
?????????????this.scope?=?_scope;
?????}
?????public?String?getScope()?{
?????????????return?this.scope;
?????}
?????public?String?getAuthor()?{
?????????????return?this.author;
?????}
?????public?String?getName()?{
?????????????return?this.name;
?????}
?????public?int?getPrice()?{
?????????????return?this.price;
?????}
}
這也很簡單,實現IComputerBook就可以,而BookStore類沒有做任何的修改,只是在static靜態模塊中增加一條數據,如代碼清單6-10所示。
代碼清單6-10 書店銷售計算機書籍
public?class?BookStore?{
?????private?final?static?ArrayList<IBook>?bookList?=?new?ArrayList<IBook>();
?????//static靜態模塊初始化數據,實際項目中一般是由持久層完成
?????static{
?????????????bookList.add(new?NovelBook("天龍八部",3200,"金庸"));
?????????????bookList.add(new?NovelBook("巴黎圣母院",5600,"雨果"));
?????????????bookList.add(new?NovelBook("悲慘世界",3500,"雨果"));
?????????????bookList.add(new?NovelBook("金瓶梅",4300,"蘭陵笑笑生"));
?????????????//增加計算機書籍
?????????????bookList.add(new?ComputerBook("Think?in?Java",4300,"Bruce?Eckel","編程語言"));
?????}??
?????//模擬書店賣書
?????public?static?void?main(String[]?args)?{
?????????????NumberFormat?formatter?=?NumberFormat.getCurrencyInstance();
?????????????formatter.setMaximumFractionDigits(2);
?????????????System.out.println("-----------書店賣出去的書籍記錄如下:-----------");
?????????????for(IBook?book:bookList){
???????????????????????System.out.println("書籍名稱:"?+?book.getName()+"\t書籍作者:"?+?book.getAuthor()+?"\t書籍價格:"?+?formatter.format?(book.getPrice()/100.0)+"元");
?????????????}
?????}
}
書店開始銷售計算機書籍,運行結果如下所示。
--------------------書店賣出去的書籍記錄如下:---------------------
書籍名稱:天龍八部 書籍作者:金庸 書籍價格:¥32.00元
書籍名稱:巴黎圣母院 書籍作者:雨果 書籍價格:¥56.00元
書籍名稱:悲慘世界 書籍作者:雨果 書籍價格:¥35.00元
書籍名稱:金瓶梅 書籍作者:蘭陵笑笑生 書籍價格:¥43.00元
書籍名稱:Think in Java 書籍作者:Bruce Eckel 書籍價格:¥43.00元
如果我是負責維護的,我就非常樂意做這樣的事情,簡單而且不需要與其他的業務進行耦合。我唯一需要做的事情就是在原有的代碼上添磚加瓦,然后就可以實現業務的變化。我們來看看這段代碼有哪幾層含義。
首先,ComputerBook類必須實現IBook的三個方法,是通過IComputerBook接口傳遞進來的約束,也就是我們制定的IBook接口對擴展類ComputerBook產生了約束力,正是由于該約束力,BookStore類才不需要進行大量的修改。
其次,如果原有的程序設計采用的不是接口,而是實現類,那會出現什么問題呢?我們把 BookStore類中的私有變量bookList修改一下,如下面的代碼所示。
private?final?static?ArrayList<NovelBook>?bookList?=?new?ArrayList<NovelBook>();
把原有IBook的依賴修改為對NovelBook實現類的依賴,想想看,我們這次的擴展是否還能繼續下去呢?一旦這樣設計,我們就根本沒有辦法擴展,需要修改原有的業務邏輯(也就是main方法),這樣的擴展基本上就是形同虛設。
最后,如果我們在IBook上增加一個方法getScope,是否可以呢?答案是不可以,因為原有的實現類NovelBook已經在投產運行中,它不需要該方法,而且接口是與其他模塊交流的契約,修改契約就等于讓其他模塊修改。因此,接口或抽象類一旦定義,就應該立即執行,不能有修改接口的思想,除非是徹底的大返工。
所以,要實現對擴展開放,首要的前提條件就是抽象約束。
2. 元數據(metadata)控制模塊行為
編程是一個很苦很累的活,那怎么才能減輕我們的壓力呢?答案是盡量使用元數據來控制程序的行為,減少重復開發。什么是元數據?用來描述環境和數據的數據,通俗地說就是配置參數,參數可以從文件中獲得,也可以從數據庫中獲得。舉個非常簡單的例子,login方法中提供了這樣的邏輯:先檢查IP地址是否在允許訪問的列表中,然后再決定是否需要到數據庫中驗證密碼(如果采用SSH架構,則可以通過Struts的攔截器來實現),該行為就是一個典型的元數據控制模塊行為的例子,其中達到極致的就是控制反轉(Inversion of Control),使用最多的就是Spring容器,在SpringContext配置文件中,基本配置如代碼清單6-11所示。
代碼清單6-11 SpringContext的基本配置文件
<bean?id="father"?class="xxx.xxx.xxx.Father"?/>
<bean?id="xx"?class="xxx.xxx.xxx.xxx">
?????????<property?name="biz"?ref="father"></property>
</bean>
然后,通過建立一個Father類的子類Son,完成一個新的業務,同時修改SpringContext文件,修改后的文件如代碼清單6-12所示。
代碼清單6-12 擴展后的SpringContext配置文件
<bean?id="son"?class="xxx.xxx.xxx.Son"?/>
<bean?id="xx"?class="xxx.xxx.xxx.xxx">
????????<property?name="biz"?ref="son"></property>
</bean>
通過擴展一個子類,修改配置文件,完成了業務變化,這也是采用框架的好處。
3. 制定項目章程
在一個團隊中,建立項目章程是非常重要的,因為章程中指定了所有人員都必須遵守的約定,對項目來說,約定優于配置。相信大家都做過項目,會發現一個項目會產生非常多的配置文件。舉個簡單的例子,以SSH項目開發為例,一個項目中的Bean配置文件就非常多,管理非常麻煩。如果需要擴展,就需要增加子類,并修改SpringContext文件。然而,如果你在項目中指定這樣一個章程:所有的Bean都自動注入,使用Annotation進行裝配,進行擴展時,甚至只用寫一個子類,然后由持久層生成對象,其他的都不需要修改,這就需要項目內約束,每個項目成員都必須遵守,該方法需要一個團隊有較高的自覺性,需要一個較長時間的磨合,一旦項目成員都熟悉這樣的規則,比通過接口或抽象類進行約束效率更高,而且擴展性一點也沒有減少。
4. 封裝變化
對變化的封裝包含兩層含義:第一,將相同的變化封裝到一個接口或抽象類中;第二,將不同的變化封裝到不同的接口或抽象類中,不應該有兩個不同的變化出現在同一個接口或抽象類中。封裝變化,也就是受保護的變化(protected variations),找出預計有變化或不穩定的點,我們為這些變化點創建穩定的接口,準確地講是封裝可能發生的變化,一旦預測到或“第六感”發覺有變化,就可以進行封裝,23個設計模式都是從各個不同的角度對變化進行封裝的,我們會在各個模式中逐步講解。
- 前言
- 第一部分 大旗不揮,誰敢沖鋒——6大設計原則全新解讀
- 第1章 單一職責原則
- 1.2 絕殺技,打破你的傳統思維
- 1.3 我單純,所以我快樂
- 1.4 最佳實踐
- 第2章 里氏替換原則
- 2.2 糾紛不斷,規則壓制
- 2.3 最佳實踐
- 第3章 依賴倒置原則
- 3.2 言而無信,你太需要契約
- 3.3 依賴的三種寫法
- 3.4 最佳實踐
- 第4章 接口隔離原則
- 4.2 美女何其多,觀點各不同
- 4.3 保證接口的純潔性
- 4.4 最佳實踐
- 第5章 迪米特法則
- 5.2 我的知識你知道得越少越好
- 5.3 最佳實踐
- 第6章 開閉原則
- 6.2 開閉原則的廬山真面目
- 6.3 為什么要采用開閉原則
- 6.4 如何使用開閉原則
- 6.5 最佳實踐
- 第二部分 真刀實槍 ——23種設計模式完美演繹
- 第7章 單例模式
- 7.2 單例模式的定義
- 7.3 單例模式的應用
- 7.4 單例模式的擴展
- 7.5 最佳實踐
- 第8章 工廠方法模式
- 8.2 工廠方法模式的定義
- 8.3 工廠方法模式的應用
- 8.4 工廠方法模式的擴展
- 8.5 最佳實踐
- 第9章 抽象工廠模式
- 9.2 抽象工廠模式的定義
- 9.3 抽象工廠模式的應用
- 9.4 最佳實踐
- 第10章 模板方法模式
- 10.2 模板方法模式的定義
- 10.3 模板方法模式的應用
- 10.4 模板方法模式的擴展
- 10.5 最佳實踐
- 第11章 建造者模式
- 11.2 建造者模式的定義
- 11.3 建造者模式的應用
- 11.4 建造者模式的擴展
- 11.5 最佳實踐
- 第12章 代理模式
- 12.2 代理模式的定義
- 12.3 代理模式的應用
- 12.4 代理模式的擴展
- 12.5 最佳實踐
- 第13章 原型模式
- 13.2 原型模式的定義
- 13.3 原型模式的應用
- 13.4 原型模式的注意事項
- 13.5 最佳實踐
- 第14章 中介者模式
- 14.2 中介者模式的定義
- 14.3 中介者模式的應用
- 14.4 中介者模式的實際應用
- 14.5 最佳實踐
- 第15章 命令模式
- 15.2 命令模式的定義
- 15.3 命令模式的應用
- 15.4 命令模式的擴展
- 15.5 最佳實踐
- 第16章 責任鏈模式
- 16.2 責任鏈模式的定義
- 16.3 責任鏈模式的應用
- 16.4 最佳實踐
- 第17章 裝飾模式
- 17.2 裝飾模式的定義
- 17.3 裝飾模式應用
- 17.4 最佳實踐
- 第18章 策略模式
- 18.2 策略模式的定義
- 18.3 策略模式的應用
- 18.4 策略模式的擴展
- 18.5 最佳實踐
- 第19章 適配器模式
- 19.2 適配器模式的定義
- 19.3 適配器模式的應用
- 19.4 適配器模式的擴展
- 19.5 最佳實踐
- 第20章 迭代器模式
- 20.2 迭代器模式的定義
- 20.3 迭代器模式的應用
- 20.4 最佳實踐
- 第21章 組合模式
- 21.2 組合模式的定義
- 21.3 組合模式的應用
- 21.4 組合模式的擴展
- 21.5 最佳實踐
- 第22章 觀察者模式
- 22.2 觀察者模式的定義
- 22.3 觀察者模式的應用
- 22.4 觀察者模式的擴展
- 22.5 最佳實踐
- 第23章 門面模式
- 23.2 門面模式的定義
- 23.3 門面模式的應用
- 23.4 門面模式的注意事項
- 23.5 最佳實踐
- 第24章 備忘錄模式
- 24.2 備忘錄模式的定義
- 24.3 備忘錄模式的應用
- 24.4 備忘錄模式的擴展
- 24.5 最佳實踐
- 第25章 訪問者模式
- 25.2 訪問者模式的定義
- 25.3 訪問者模式的應用
- 25.4 訪問者模式的擴展
- 25.5 最佳實踐
- 第26章 狀態模式
- 26.2 狀態模式的定義
- 26.3 狀態模式的應用
- 第27章 解釋器模式
- 27.2 解釋器模式的定義
- 27.3 解釋器模式的應用
- 27.4 最佳實踐
- 第28章 享元模式
- 28.2 享元模式的定義
- 28.3 享元模式的應用
- 28.4 享元模式的擴展
- 28.5 最佳實踐
- 第29章 橋梁模式
- 29.2 橋梁模式的定義
- 29.3 橋梁模式的應用
- 29.4 最佳實踐
- 第三部分 誰的地盤誰做主 ——設計模式PK
- 第30章 創建類模式大PK
- 30.1 工廠方法模式VS建造者模式
- 30.2 抽象工廠模式VS建造者模式
- 第31章 結構類模式大PK
- 31.1 代理模式VS裝飾模式
- 31.2 裝飾模式VS適配器模式
- 第32章 行為類模式大PK
- 32.1 命令模式VS策略模式
- 32.2 策略模式VS狀態模式
- 32.3 觀察者模式VS責任鏈模式
- 第33章 跨戰區PK
- 33.1 策略模式VS橋梁模式
- 33.2 門面模式VS中介者模式
- 33.3 包裝模式群PK
- 第四部分 完美世界 ——設計模式混編
- 第34章 命令模式+責任鏈模式
- 34.2 混編小結
- 第35章 工廠方法模式+策略模式
- 35.2 混編小結
- 第36章 觀察者模式+中介者模式
- 36.2 混編小結
- 第五部分 擴展篇
- 第37章 MVC框架
- 37.2 最佳實踐
- 第38章 新模式
- 38.1 規格模式
- 38.2 對象池模式
- 38.3 雇工模式
- 38.4 黑板模式
- 38.5 空對象模式
- 附錄 23種設計模式彩圖