開閉原則

定義:軟件實體(類,模板愁憔,方法)可以擴展腕扶,但是不可修改。即對于擴展是開放的吨掌,對于更改是封閉的半抱。面對需求,對程序的改動是通過增加新代碼進行的膜宋,而不是更改現(xiàn)有的代碼窿侈。

一個軟件產(chǎn)品只要在生命期內(nèi), 都會發(fā)生變化秋茫, 既然變化是一個既定的事實史简, 我們就應(yīng)該在設(shè)計時盡量適應(yīng)這些變化, 以提高項目的穩(wěn)定性和靈活性肛著, 真正實現(xiàn)“擁抱變化”圆兵。

開閉原則告訴我們應(yīng)盡量通過擴展軟件實體的行為來實現(xiàn)變化, 而不是通過修改已有的代碼來完成變化枢贿, 它是為軟件實體的未來事件而制定的對現(xiàn)行開發(fā)設(shè)計進行約束的一個原則殉农。

我們舉例說明什么是開閉原則, 以書店銷售書籍為例萨咕, 其類圖如圖所示:


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

public class NovelBook implements IBook {
    //書籍名稱
    private String name;
    //書籍的價格
    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;
    }
    //獲得作者是誰
    public String getAuthor() {
        return this.author;
    }
    //書籍叫什么名字
    public String getName() {
        return this.name;
    }
    //獲得書籍的價格
    public int getPrice() {
            return this.price;
    }
}
public class BookStore {
    private final static ArrayList<IBook> bookList = new ArrayList<IBook>();
    //static靜態(tài)模塊初始化數(shù)據(jù)统抬, 實際項目中一般是由持久層完成
    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書籍價格: "+book.getPrice()); 
        }
    }
}

項目投產(chǎn)了, 書籍正常銷售出去危队, 書店也贏利了聪建。 從2008年開始, 全球經(jīng)濟開始下滑茫陆,對零售業(yè)影響比較大金麸, 書店為了生存開始打折銷售: 所有40元以上的書籍9折銷售, 其他的8折銷售簿盅。 對已經(jīng)投產(chǎn)的項目來說挥下, 這就是一個變化揍魂, 我們應(yīng)該如何應(yīng)對這樣一個需求變化?有如下三種方法可以解決這個問題:

1)修改接口

在IBook上新增加一個方法getOffPrice()棚瘟, 專門用于進行打折處理现斋, 所有的實現(xiàn)類實現(xiàn)該方法。 修改的后果是偎蘸, 實現(xiàn)類ovelBook要修改庄蹋, BookStore中的main方法也修改, 同時IBook作為接口應(yīng)該是穩(wěn)定且可靠的迷雪, 不應(yīng)該經(jīng)常發(fā)生變化限书, 否則接口作為契約的作用就失去了效能。 因此章咧, 該方案否定倦西。

2)修改實現(xiàn)類

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

3)通過擴展實現(xiàn)變化

增加一個子類OffNovelBook, 覆寫getPrice方法载荔, 高層次的模塊(也就是static靜態(tài)模塊區(qū)) 通過OffNovelBook類產(chǎn)生新的對象盾饮, 完成業(yè)務(wù)變化對系統(tǒng)的最小化開發(fā)。 好辦法懒熙, 修改也少丘损, 風(fēng)險也小。

image.png

OffNovelBook類繼承了NovelBook工扎, 并覆寫了getPrice方法徘钥, 不修改原有的代碼。 新增加的子類OffNovelBook代碼如下:

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

很簡單呈础, 僅僅覆寫了getPrice方法舆驶, 通過擴展完成了新增加的業(yè)務(wù)。

為什么要使用開閉原則

每個事物的誕生都有它存在的必要性而钞, 存在即合理沙廉, 那開閉原則的存在也是合理的, 為什么這么說呢臼节?

1. 開閉原則對測試的影響

所有已經(jīng)投產(chǎn)的代碼都是有意義的撬陵, 并且都受系統(tǒng)規(guī)則的約束, 這樣的代碼都要經(jīng)過“千錘百煉”的測試過程官疲, 不僅保證邏輯是正確的袱结, 還要保證苛刻條件(高壓力、 異常途凫、 錯誤) 下不產(chǎn)生“有毒代碼”(Poisonous Code) 垢夹, 因此有變化提出時, 我們就需要考慮一下维费,原有的健壯代碼是否可以不修改果元, 僅僅通過擴展實現(xiàn)變化呢? 否則犀盟, 就需要把原有的測試過程回籠一遍而晒, 需要進行單元測試、 功能測試阅畴、 集成測試甚至是驗收測試倡怎, 現(xiàn)在雖然在大力提倡自動化測試工具, 但是仍然代替不了人工的測試工作贱枣。

2. 開閉原則可以提高復(fù)用性

在面向?qū)ο蟮脑O(shè)計中监署, 所有的邏輯都是從原子邏輯組合而來的, 而不是在一個類中獨立實現(xiàn)一個業(yè)務(wù)邏輯纽哥。 只有這樣代碼才可以復(fù)用钠乏,粒度越小, 被復(fù)用的可能性就越大春塌。 那為什么要復(fù)用呢晓避? 減少代碼量, 避免相同的邏輯分散在多個角落只壳, 避免日后的維護人員為了修改一個微小的缺陷或增加新功能而要在整個項目中到處查找相關(guān)的代碼俏拱, 然后發(fā)出對開發(fā)人員“極度失望”的感慨。 那怎么才能提高復(fù)用率呢吼句? 縮小邏輯粒度彰触, 直到一個邏輯不可再拆分為止。

3. 開閉原則可以提高可維護性

一款軟件投產(chǎn)后命辖, 維護人員的工作不僅僅是對數(shù)據(jù)進行維護况毅, 還可能要對程序進行擴展分蓖, 維護人員最樂意做的事情就是擴展一個類, 而不是修改一個類尔许, 甭管原有的代碼寫得多么優(yōu)秀還是多么糟糕么鹤, 讓維護人員讀懂原有的代碼, 然后再修改味廊, 是一件很痛苦的事情蒸甜, 不要讓他在原有的代碼海洋里游弋完畢后再修改, 那是對維護人員的一種折磨和摧殘余佛。

4. 面向?qū)ο箝_發(fā)的要求

萬物皆對象柠新, 我們需要把所有的事物都抽象成對象, 然后針對對象進行操作辉巡, 但是萬物皆運動恨憎, 有運動就有變化, 有變化就要有策略去應(yīng)對郊楣, 怎么快速應(yīng)對呢憔恳? 這就需要在設(shè)計之初考慮到所有可能變化的因素, 然后留下接口净蚤, 等待“可能”轉(zhuǎn)變?yōu)椤艾F(xiàn)實”钥组。

怎么使用開閉原則

1. 抽象約束

抽象是對一組事物的通用描述, 沒有具體的實現(xiàn)今瀑, 也就表示它可以有非常多的可能性程梦,可以跟隨需求的變化而變化。 因此橘荠, 通過接口或抽象類可以約束一組可能變化的行為作烟, 并且能夠?qū)崿F(xiàn)對擴展開放, 其包含三層含義: 第一砾医, 通過接口或抽象類約束擴展, 對擴展進行邊界限定衣厘, 不允許出現(xiàn)在接口或抽象類中不存在的public方法如蚜; 第二, 參數(shù)類型影暴、 引用對象盡量使用接口或者抽象類错邦, 而不是實現(xiàn)類; 第三型宙, 抽象層盡量保持穩(wěn)定撬呢, 一旦確定即不允許修改。

2. 元數(shù)據(jù)(metadata) 控制模塊行為

編程是一個很苦很累的活妆兑, 那怎么才能減輕我們的壓力呢魂拦? 答案是盡量使用元數(shù)據(jù)來控制程序的行為毛仪, 減少重復(fù)開發(fā)。 什么是元數(shù)據(jù)芯勘? 用來描述環(huán)境和數(shù)據(jù)的數(shù)據(jù)箱靴, 通俗地說就是配置參數(shù), 參數(shù)可以從文件中獲得荷愕, 也可以從數(shù)據(jù)庫中獲得衡怀。典型的元數(shù)據(jù)控制模塊行為的例子, 其中達到極致的就是控制反轉(zhuǎn)(Inversion of Control) 安疗,使用最多的就是Spring容器抛杨, 擴展一個子類, 修改SpringContext配置文件荐类, 完成了業(yè)務(wù)變化怖现, 這也是采用框架的好處。

3. 制定項目章程

在一個團隊中掉冶, 建立項目章程是非常重要的真竖, 因為章程中指定了所有人員都必須遵守的約定, 對項目來說厌小, 約定優(yōu)于配置恢共。 相信大家都做過項目, 會發(fā)現(xiàn)一個項目會產(chǎn)生非常多的配置文件璧亚。舉個簡單的例子讨韭, 以SSH項目開發(fā)為例, 一個項目中的Bean配置文件就非常多癣蟋,管理非常麻煩透硝。 如果需要擴展, 就需要增加子類疯搅, 并修改SpringContext文件濒生。 然而, 如果你在項目中指定這樣一個章程: 所有的Bean都自動注入幔欧, 使用Annotation進行裝配罪治, 進行擴展時, 甚至只用寫一個子類礁蔗, 然后由持久層生成對象觉义, 其他的都不需要修改, 這就需要項目內(nèi)約束浴井, 每個項目成員都必須遵守晒骇, 該方法需要一個團隊有較高的自覺性, 需要一個較長時間的磨合, 一旦項目成員都熟悉這樣的規(guī)則洪囤, 比通過接口或抽象類進行約束效率更高徒坡, 而且擴展性一點也沒有減少。

4. 封裝變化

對變化的封裝包含兩層含義: 第一箍鼓, 將相同的變化封裝到一個接口或抽象類中崭参; 第二,將不同的變化封裝到不同的接口或抽象類中款咖, 不應(yīng)該有兩個不同的變化出現(xiàn)在同一個接口或抽象類中何暮。 封裝變化, 也就是受保護的變化(protected variations) 铐殃, 找出預(yù)計有變化或不穩(wěn)定的點海洼, 我們?yōu)檫@些變化點創(chuàng)建穩(wěn)定的接口, 準(zhǔn)確地講是封裝可能發(fā)生的變化富腊, 一旦預(yù)測到或“第六感”發(fā)覺有變化坏逢, 就可以進行封裝。

總結(jié):開放-封閉原則是面向?qū)ο笤O(shè)計的核心所在赘被。遵循這個原則可以帶來面向?qū)ο蠹夹g(shù)所聲稱的巨大好處是整,也就是可維護、可擴展民假、可復(fù)用浮入、靈活性好。開發(fā)人員應(yīng)該僅對程序中頻繁出現(xiàn)變化的那些部分做出抽象羊异,然而事秀,對于程序中的每個部分都刻意地進行抽象同樣不是一個好主意。拒絕不成熟的抽象和抽象本身一樣重要野舶。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末易迹,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子平道,更是在濱河造成了極大的恐慌睹欲,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,734評論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件一屋,死亡現(xiàn)場離奇詭異窘疮,居然都是意外死亡,警方通過查閱死者的電腦和手機陆淀,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,931評論 3 394
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來先嬉,“玉大人轧苫,你說我怎么就攤上這事。” “怎么了含懊?”我有些...
    開封第一講書人閱讀 164,133評論 0 354
  • 文/不壞的土叔 我叫張陵身冬,是天一觀的道長。 經(jīng)常有香客問我岔乔,道長酥筝,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,532評論 1 293
  • 正文 為了忘掉前任雏门,我火速辦了婚禮嘿歌,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘茁影。我一直安慰自己宙帝,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,585評論 6 392
  • 文/花漫 我一把揭開白布募闲。 她就那樣靜靜地躺著步脓,像睡著了一般。 火紅的嫁衣襯著肌膚如雪浩螺。 梳的紋絲不亂的頭發(fā)上靴患,一...
    開封第一講書人閱讀 51,462評論 1 302
  • 那天,我揣著相機與錄音要出,去河邊找鬼鸳君。 笑死,一個胖子當(dāng)著我的面吹牛厨幻,可吹牛的內(nèi)容都是我干的相嵌。 我是一名探鬼主播,決...
    沈念sama閱讀 40,262評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼况脆,長吁一口氣:“原來是場噩夢啊……” “哼饭宾!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起格了,我...
    開封第一講書人閱讀 39,153評論 0 276
  • 序言:老撾萬榮一對情侶失蹤看铆,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后盛末,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體弹惦,經(jīng)...
    沈念sama閱讀 45,587評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,792評論 3 336
  • 正文 我和宋清朗相戀三年悄但,在試婚紗的時候發(fā)現(xiàn)自己被綠了棠隐。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,919評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡檐嚣,死狀恐怖助泽,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤嗡贺,帶...
    沈念sama閱讀 35,635評論 5 345
  • 正文 年R本政府宣布隐解,位于F島的核電站,受9級特大地震影響诫睬,放射性物質(zhì)發(fā)生泄漏煞茫。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,237評論 3 329
  • 文/蒙蒙 一摄凡、第九天 我趴在偏房一處隱蔽的房頂上張望续徽。 院中可真熱鬧,春花似錦架谎、人聲如沸炸宵。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,855評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽土全。三九已至,卻和暖如春会涎,著一層夾襖步出監(jiān)牢的瞬間裹匙,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,983評論 1 269
  • 我被黑心中介騙來泰國打工末秃, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留概页,地道東北人。 一個月前我還...
    沈念sama閱讀 48,048評論 3 370
  • 正文 我出身青樓练慕,卻偏偏與公主長得像惰匙,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子铃将,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,864評論 2 354

推薦閱讀更多精彩內(nèi)容