官术网_书友最值得收藏!

6.2 開閉原則的廬山真面目

開閉原則的定義已經(jīng)非常明確地告訴我們:軟件實(shí)體應(yīng)該對(duì)擴(kuò)展開放,對(duì)修改關(guān)閉,其含義是說(shuō)一個(gè)軟件實(shí)體應(yīng)該通過擴(kuò)展來(lái)實(shí)現(xiàn)變化,而不是通過修改已有的代碼來(lái)實(shí)現(xiàn)變化。那什么又是軟件實(shí)體呢?軟實(shí)體包括以下幾個(gè)部分:

?項(xiàng)目或軟件產(chǎn)品中按照一定的邏輯規(guī)則劃分的模塊。

?抽象和類。

?方法。

一個(gè)軟件產(chǎn)品只要在生命期內(nèi),都會(huì)發(fā)生變化,既然變化是一個(gè)既定的事實(shí),我們就應(yīng)該在設(shè)計(jì)時(shí)盡量適應(yīng)這些變化,以提高項(xiàng)目的穩(wěn)定性和靈活性,真正實(shí)現(xiàn)“擁抱變化”。開閉原則告訴我們應(yīng)盡量通過擴(kuò)展軟件實(shí)體的行為來(lái)實(shí)現(xiàn)變化,而不是通過修改已有的代碼來(lái)完成變化,它是為軟件實(shí)體的未來(lái)事件而制定的對(duì)現(xiàn)行開發(fā)設(shè)計(jì)進(jìn)行約束的一個(gè)原則。我們舉例說(shuō)明什么是開閉原則,以書店銷售書籍為例,其類圖如圖6-1所示。

圖6-1 書店售書類圖

IBook定義了數(shù)據(jù)的三個(gè)屬性:名稱、價(jià)格和作者。小說(shuō)類NovelBook是一個(gè)具體的實(shí)現(xiàn)類,是所有小說(shuō)書籍的總稱,BookStore指的是書店,IBook接口如代碼清單6-1所示。

代碼清單6-1 書籍接口

public interface IBook {
     //書籍有名稱
     public String getName();
     //書籍有售價(jià)
     public int getPrice();
     //書籍有作者
     public String getAuthor();
}

目前書店只出售小說(shuō)類書籍,小說(shuō)類如代碼清單6-2所示。

代碼清單6-2 小說(shuō)類

public class NovelBook implements IBook {
     //書籍名稱
     private String name;
     //書籍的價(jià)格
     private int price;
     //書籍的作者
     private String author;
     //通過構(gòu)造函數(shù)傳遞書籍?dāng)?shù)據(jù)
     public NovelBook(String _name,int _price,String _author){
             this.name=_name;
             this.price=_price;
             this.author=_author;
     }
     //獲得作者是誰(shuí)
     public String getAuthor() {
             return this.author;
     }
     //書籍叫什么名字
     public String getName() {
             return this.name;
     }
     //獲得書籍的價(jià)格
     public int getPrice() {
             return this.price;
     }
}

注意 我們把價(jià)格定義為int類型并不是錯(cuò)誤,在非金融類項(xiàng)目中對(duì)貨幣處理時(shí),一般取2位精度,通常的設(shè)計(jì)方法是在運(yùn)算過程中擴(kuò)大100倍,在需要展示時(shí)再縮小100倍,減少精度帶來(lái)的誤差。

書店售書的過程如代碼清單6-3所示。

代碼清單6-3 書店售書類

public class BookStore {
     private final static ArrayList<IBook> bookList=new ArrayList<IBook>();
     //static靜態(tài)模塊初始化數(shù)據(jù),實(shí)際項(xiàng)目中一般是由持久層完成
     static{
             bookList.add(new NovelBook("天龍八部",3200,"金庸"));
             bookList.add(new NovelBook("巴黎圣母院",5600,"雨果"));
             bookList.add(new NovelBook("悲慘世界",3500,"雨果"));
             bookList.add(new NovelBook("金瓶梅",4300,"蘭陵笑笑生"));
     }
     //模擬書店買書
     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書籍價(jià)格:"+formatter.format
                     (book.getPrice()/100.0)+"元");
             }
     }
}

在BookStore中聲明了一個(gè)靜態(tài)模塊,實(shí)現(xiàn)了數(shù)據(jù)的初始化,這部分應(yīng)該是從持久層產(chǎn)生的,由持久層框架進(jìn)行管理,運(yùn)行結(jié)果如下:

------------書店賣出去的書籍記錄如下:---------------------
書籍名稱:天龍八部   書籍作者:金庸       書籍價(jià)格:¥32.00元
書籍名稱:巴黎圣母院 書籍作者:雨果       書籍價(jià)格:¥56.00元
書籍名稱:悲慘世界   書籍作者:雨果       書籍價(jià)格:¥35.00元
書籍名稱:金瓶梅     書籍作者:蘭陵笑笑生 書籍價(jià)格:¥43.00元

項(xiàng)目投產(chǎn)了,書籍正常銷售出去,書店也盈利了。從2008年開始,全球經(jīng)濟(jì)開始下滑,對(duì)零售業(yè)影響比較大,書店為了生存開始打折銷售:所有40元以上的書籍9折銷售,其他的8折銷售。對(duì)已經(jīng)投產(chǎn)的項(xiàng)目來(lái)說(shuō),這就是一個(gè)變化,我們應(yīng)該如何應(yīng)對(duì)這樣一個(gè)需求變化?有如下三種方法可以解決這個(gè)問題:

?修改接口

在IBook上新增加一個(gè)方法getOffPrice(),專門用于進(jìn)行打折處理,所有的實(shí)現(xiàn)類實(shí)現(xiàn)該方法。但是這樣修改的后果就是,實(shí)現(xiàn)類NovelBook要修改,BookStore中的main方法也修改,同時(shí)IBook作為接口應(yīng)該是穩(wěn)定且可靠的,不應(yīng)該經(jīng)常發(fā)生變化,否則接口作為契約的作用就失去了效能。因此,該方案否定。

?修改實(shí)現(xiàn)類

修改NovelBook類中的方法,直接在getPrice()中實(shí)現(xiàn)打折處理,好辦法,我相信大家在項(xiàng)目中經(jīng)常使用的就是這樣的辦法,通過class文件替換的方式可以完成部分業(yè)務(wù)變化(或是缺陷修復(fù))。該方法在項(xiàng)目有明確的章程(團(tuán)隊(duì)內(nèi)約束)或優(yōu)良的架構(gòu)設(shè)計(jì)時(shí),是一個(gè)非常優(yōu)秀的方法,但是該方法還是有缺陷的。例如采購(gòu)書籍人員也是要看價(jià)格的,由于該方法已經(jīng)實(shí)現(xiàn)了打折處理價(jià)格,因此采購(gòu)人員看到的也是打折后的價(jià)格,會(huì)因信息不對(duì)稱而出現(xiàn)決策失誤的情況。因此,該方案也不是一個(gè)最優(yōu)的方案。

?通過擴(kuò)展實(shí)現(xiàn)變化

增加一個(gè)子類OffNovelBook,覆寫getPrice方法,高層次的模塊(也就是static靜態(tài)模塊區(qū))通過OffNovelBook類產(chǎn)生新的對(duì)象,完成業(yè)務(wù)變化對(duì)系統(tǒng)的最小化開發(fā)。好辦法,修改也少,風(fēng)險(xiǎn)也小,修改后的類圖如圖6-2所示。

圖6-2 擴(kuò)展后的書店售書類圖

OffNovelBook類繼承了NovelBook,并覆寫了getPrice方法,不修改原有的代碼。新增加的子類OffNovelBook如代碼清單6-4所示。

代碼清單6-4 打折銷售的小說(shuō)類

public class OffNovelBook extends NovelBook {
     public OffNovelBook(String _name,int _price,String _author){
             super(_name,_price,_author);
     }
     //覆寫銷售價(jià)格
     @Override
     public int getPrice(){
             //原價(jià)
             int selfPrice=super.getPrice();
             int offPrice=0;
             if(selfPrice>4000){ //原價(jià)大于40元,則打9折
                     offPrice=selfPrice * 90 /100;
             }else{
                     offPrice=selfPrice * 80 /100;
             }
             return offPrice;
     }
}

很簡(jiǎn)單,僅僅覆寫了getPrice方法,通過擴(kuò)展完成了新增加的業(yè)務(wù)。書店類BookStore需要依賴子類,代碼稍作修改,如代碼清單6-5所示。

代碼清單6-5 書店打折銷售類

public class BookStore {
     private final static ArrayList<IBook> bookList=new ArrayList<IBook>();
     //static靜態(tài)模塊初始化數(shù)據(jù),實(shí)際項(xiàng)目中一般是由持久層完成
     static{
             bookList.add(new OffNovelBook("天龍八部",3200,"金庸"));
             bookList.add(new OffNovelBook("巴黎圣母院",5600,"雨果"));
             bookList.add(new OffNovelBook("悲慘世界",3500,"雨果"));
             bookList.add(new OffNovelBook("金瓶梅",4300,"蘭陵笑笑生"));
     }
     //模擬書店買書
     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書籍價(jià)格:"+formatter.format
                      (book.getPrice()/100.0)+"元");
             }
     }
}

我們只修改了粗體部分,其他的部分沒有任何改動(dòng),運(yùn)行結(jié)果如下所示。

------------書店買出去的書籍記錄如下:---------------------
書籍名稱:天龍八部     書籍作者:金庸       書籍價(jià)格:¥25.60元
書籍名稱:巴黎圣母院   書籍作者:雨果       書籍價(jià)格:¥50.40元
書籍名稱:悲慘世界     書籍作者:雨果       書籍價(jià)格:¥28.00元
書籍名稱:金瓶梅       書籍作者:蘭陵笑笑生 書籍價(jià)格:¥38.70元

OK,打折銷售開發(fā)完成了。看到這里,各位可能有想法了:增加了一個(gè)OffNoveBook類后,你的業(yè)務(wù)邏輯還是修改了,你修改了static靜態(tài)模塊區(qū)域。這部分確實(shí)修改了,該部分屬于高層次的模塊,是由持久層產(chǎn)生的,在業(yè)務(wù)規(guī)則改變的情況下高層模塊必須有部分改變以適應(yīng)新業(yè)務(wù),改變要盡量地少,防止變化風(fēng)險(xiǎn)的擴(kuò)散。

注意 開閉原則對(duì)擴(kuò)展開放,對(duì)修改關(guān)閉,并不意味著不做任何修改,低層模塊的變更,必然要有高層模塊進(jìn)行耦合,否則就是一個(gè)孤立無(wú)意義的代碼片段。

我們可以把變化歸納為以下三種類型:

?邏輯變化

只變化一個(gè)邏輯,而不涉及其他模塊,比如原有的一個(gè)算法是a*b+c,現(xiàn)在需要修改為a*b*c,可以通過修改原有類中的方法的方式來(lái)完成,前提條件是所有依賴或關(guān)聯(lián)類都按照相同的邏輯處理。

?子模塊變化

一個(gè)模塊變化,會(huì)對(duì)其他的模塊產(chǎn)生影響,特別是一個(gè)低層次的模塊變化必然引起高層模塊的變化,因此在通過擴(kuò)展完成變化時(shí),高層次的模塊修改是必然的,剛剛的書籍打折處理就是類似的處理模塊,該部分的變化甚至?xí)鸾缑娴淖兓?/p>

?可見視圖變化

可見視圖是提供給客戶使用的界面,如JSP程序、Swing界面等,該部分的變化一般會(huì)引起連鎖反應(yīng)(特別是在國(guó)內(nèi)做項(xiàng)目,做歐美的外包項(xiàng)目一般不會(huì)影響太大)。如果僅僅是界面上按鈕、文字的重新排布倒是簡(jiǎn)單,最司空見慣的是業(yè)務(wù)耦合變化,什么意思呢?一個(gè)展示數(shù)據(jù)的列表,按照原有的需求是6列,突然有一天要增加1列,而且這一列要跨N張表,處理M個(gè)邏輯才能展現(xiàn)出來(lái),這樣的變化是比較恐怖的,但還是可以通過擴(kuò)展來(lái)完成變化,這就要看我們?cè)械脑O(shè)計(jì)是否靈活。

我們?cè)賮?lái)回顧一下書店銷售書籍的程序,首先是我們有一個(gè)還算靈活的設(shè)計(jì)(不靈活是什么樣子?BookStore中所有使用到IBook的地方全部修改為實(shí)現(xiàn)類,然后再擴(kuò)展一個(gè)ComputerBook書籍,你就知道什么是不靈活了);然后有一個(gè)需求變化,我們通過擴(kuò)展一個(gè)子類擁抱了變化;最后把子類投入運(yùn)行環(huán)境中,新邏輯正式投產(chǎn)。通過分析,我們發(fā)現(xiàn)并沒有修改原有的模塊代碼,IBook接口沒有改變,NovelBook類沒有改變,這屬于已有的業(yè)務(wù)代碼,我們保持了歷史的純潔性。放棄修改歷史的想法吧,一個(gè)項(xiàng)目的基本路徑應(yīng)該是這樣的:項(xiàng)目開發(fā)、重構(gòu)、測(cè)試、投產(chǎn)、運(yùn)維,其中的重構(gòu)可以對(duì)原有的設(shè)計(jì)和代碼進(jìn)行修改,運(yùn)維盡量減少對(duì)原有代碼的修改,保持歷史代碼的純潔性,提高系統(tǒng)的穩(wěn)定性。

主站蜘蛛池模板: 恩施市| 西林县| 榆中县| 临洮县| 乌兰浩特市| 县级市| 宁阳县| 广河县| 遂昌县| 襄樊市| 嘉禾县| 镇坪县| 科技| 佛教| 博乐市| 通榆县| 滁州市| 凤山县| 余干县| 江川县| 石屏县| 绵阳市| 菏泽市| 大安市| 武冈市| 永福县| 孝感市| 赣州市| 北宁市| 岗巴县| 漯河市| 东山县| 峨边| 张家界市| 威宁| 泗水县| 穆棱市| 城固县| 武夷山市| 永新县| 屯留县|