開閉原則(OCP)

開閉原則的定義

開閉原則是java世界里最基礎(chǔ)的設(shè)計(jì)原則,它指導(dǎo)我們?nèi)绾谓⒁粋€(gè)穩(wěn)定,靈活的系統(tǒng)。開閉原則定義如下:

Software entities like classes,modules and functions should be open for extension but closed for modifications.

一個(gè)軟件實(shí)體如類,模塊和函數(shù)應(yīng)該對(duì)擴(kuò)展開放,對(duì)修改關(guān)閉。

什么是開閉原則

開閉原則明確的告訴我們:軟件實(shí)現(xiàn)應(yīng)該對(duì)擴(kuò)展開放,對(duì)修改關(guān)閉,其含義是說一個(gè)軟件實(shí)體應(yīng)該通過擴(kuò)展來實(shí)現(xiàn)變化,而不是通過修改已有的代碼來實(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í)體的行為來實(shí)現(xiàn)變化,而不是通過修改現(xiàn)有代碼來完成變化,它是為軟件實(shí)體的未來事件而制定的對(duì)現(xiàn)行開發(fā)設(shè)計(jì)進(jìn)行約束的一個(gè)原則。

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

書店銷售書籍類圖

書籍接口:

public interface IBook{
  public String getName();
  public String getPrice();
  public String getAuthor();
}

小說類書籍:

public class NovelBook implements IBook{
   private String name;
   private int price;
   private String author;

   public NovelBook(String name,int price,String author){
     this.name = name;
     this.price = price;
     this.author = author;
   }

   public String getAutor(){
     return this.author;
   }

   public String getName(){
     return this.name;
   }  

   public int getPrice(){
     return this.price;
   } 
}

Client類:

public class Client{
   public static void main(Strings[] args){
     IBook novel = new NovelBook("笑傲江湖",100,"金庸");
     System.out.println("書籍名字:"+novel.getName()+"書籍作者:"+novel.getAuthor()+"書籍價(jià)格:"+novel.getPrice());
   }

}

項(xiàng)目投產(chǎn)生,書籍正常銷售,但是我們經(jīng)常因?yàn)楦鞣N原因,要打折來銷售書籍,這是一個(gè)變化,我們要如何應(yīng)對(duì)這樣一個(gè)需求變化呢?

我們有下面三種方法可以解決此問題:

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

  • 修改實(shí)現(xiàn)類
    修改NovelBook類的方法,直接在getPrice()方法中實(shí)現(xiàn)打折處理。此方法是有問題的,例如我們?nèi)绻鹓etPrice()方法中只需要讀取書籍的打折前的價(jià)格呢?這不是有問題嗎?當(dāng)然我們也可以再增加getOffPrice()方法,這也是可以實(shí)現(xiàn)其需求,但是這就有二個(gè)讀取價(jià)格的方法,因此,該方案也不是一個(gè)最優(yōu)方案。

  • 通過擴(kuò)展實(shí)現(xiàn)變化
    我們可以增加一個(gè)子類OffNovelBook,覆寫getPrice方法。此方法修改少,對(duì)現(xiàn)有的代碼沒有影響,風(fēng)險(xiǎn)少,是個(gè)好辦法。

    下面是修改后的類圖:

修改后的類圖

打折類:

public class OffNovelBook extends NovelBook{

   public OffNovelBook(String name,int price,String author){
      super(name,price,author);
   }

   //覆寫價(jià)格方法,當(dāng)價(jià)格大于40,就打8析,其他價(jià)格就打9析
   public int getPrice(){
     if(this.price > 40){
        return this.price * 0.8;
     }else{
        return this.price * 0.9;
     }     
   } 
}

現(xiàn)在打折銷售開發(fā)完成了,我們只是增加了一個(gè)OffNovelBook類,我們修改的代碼都是高層次的模塊,沒有修改底層模塊,代碼改變量少,可以有效的防止風(fēng)險(xiǎn)的擴(kuò)散。

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

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

  • 子模塊變化
    一人模塊變化,會(huì)對(duì)其它的模塊產(chǎn)生影響,特別是一個(gè)低層次的模塊變化必然引起高層模塊的變化,因此在通過擴(kuò)展完成變化。

為什么使用開閉原則

第一:開閉原則非常有名,只要是面向?qū)ο缶幊?,在開發(fā)時(shí)都會(huì)強(qiáng)調(diào)開閉原則

第二:開閉原則是最基礎(chǔ)的設(shè)計(jì)原則,其它的五個(gè)設(shè)計(jì)原則都是開閉原則的具體形態(tài),也就是說其它的五個(gè)設(shè)計(jì)原則是指導(dǎo)設(shè)計(jì)的工具和方法,而開閉原則才是其精神領(lǐng)袖。依照java語言的稱謂,開閉原則是抽象類,而其它的五個(gè)原則是具體的實(shí)現(xiàn)類。

第三:開閉原則可以提高復(fù)用性
在面向?qū)ο蟮脑O(shè)計(jì)中,所有的邏輯都是從原子邏輯組合而來,不是在一個(gè)類中獨(dú)立實(shí)現(xiàn)一個(gè)業(yè)務(wù)邏輯。只有這樣的代碼才可以復(fù)用,粒度越小,被復(fù)用的可能性越大。那為什么要復(fù)用呢?減少代碼的重復(fù),避免相同的邏輯分散在多個(gè)角落,減少維護(hù)人員的工作量。那怎么才能提高復(fù)用率呢?縮小邏輯粒度,直到一個(gè)邏輯不可以分為止。

第四:開閉原則可以提高維護(hù)性
一款軟件量產(chǎn)后,維護(hù)人員的工作不僅僅對(duì)數(shù)據(jù)進(jìn)行維護(hù),還可能要對(duì)程序進(jìn)行擴(kuò)展,維護(hù)人員最樂意的事是擴(kuò)展一個(gè)類,而不是修改一個(gè)類。讓維護(hù)人員讀懂原有代碼,再進(jìn)行修改,是一件非常痛苦的事情,不要讓他在原有的代碼海洋中游蕩后再修改,那是對(duì)維護(hù)人員的折磨和摧殘。

第五:面向?qū)ο箝_發(fā)的要求
萬物皆對(duì)象,我們要把所有的事物抽象成對(duì)象,然后針對(duì)對(duì)象進(jìn)行操作,但是萬物皆發(fā)展變化,有變化就要有策略去應(yīng)對(duì),怎么快速應(yīng)對(duì)呢?這就需要在設(shè)計(jì)之初考慮到所有可能變化的因素,然后留下接口,等待“可能”轉(zhuǎn)變?yōu)椤艾F(xiàn)實(shí)”。

如何使用開閉原則

第一:抽象約束

抽象是對(duì)一組事物的通用描述,沒有具體的實(shí)現(xiàn),也就表示它可以有非常多的可能性,可以跟隨需求的變化而變化。因此,通過接口或抽象類可以約束一組可能變化的行為,并且能夠?qū)崿F(xiàn)對(duì)擴(kuò)展開放,其包含三層含義:

  • 通過接口或抽象類約束擴(kuò)散,對(duì)擴(kuò)展進(jìn)行邊界限定,不允許出現(xiàn)在接口或抽象類中不存在的public方法。
  • 參數(shù)類型,引用對(duì)象盡量使用接口或抽象類,而不是實(shí)現(xiàn)類,這主要是實(shí)現(xiàn)里氏替換原則的一個(gè)要求
  • 抽象層盡量保持穩(wěn)定,一旦確定就不要修改

第二:元數(shù)據(jù)(metadata)控件模塊行為

編程是一個(gè)很苦很累的活,那怎么才能減輕壓力呢?答案是盡量使用元數(shù)據(jù)來控制程序的行為,減少重復(fù)開發(fā)。什么是元數(shù)據(jù)?用來描述環(huán)境和數(shù)據(jù)的數(shù)據(jù),通俗的說就是配置參數(shù),參數(shù)可以從文件中獲得,也可以從數(shù)據(jù)庫(kù)中獲得。

第三:制定項(xiàng)目章程

在一個(gè)團(tuán)隊(duì)中,建立項(xiàng)目章程是非常重要的,因?yàn)檎鲁淌撬腥藛T都必須遵守的約定,對(duì)項(xiàng)目來說,約定優(yōu)于配置。這比通過接口或抽象類進(jìn)行約束效率更高,而擴(kuò)展性一點(diǎn)也沒有減少。

第四:封裝變化

對(duì)變化封裝包含兩層含義:
(1) 將相同的變化封裝到一個(gè)接口或抽象類中
(2) 將不同的變化封裝到不同的接口或抽象類中,不應(yīng)該有兩個(gè)不同的變化出現(xiàn)在同一個(gè)接口或抽象類中。
封裝變化,也就是受保護(hù)的變化,找出預(yù)計(jì)有變化或不穩(wěn)定的點(diǎn),我們?yōu)檫@些變化點(diǎn)創(chuàng)建穩(wěn)定的接口。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 設(shè)計(jì)模式概述 在學(xué)習(xí)面向?qū)ο笃叽笤O(shè)計(jì)原則時(shí)需要注意以下幾點(diǎn):a) 高內(nèi)聚、低耦合和單一職能的“沖突”實(shí)際上,這兩者...
    彥幀閱讀 3,888評(píng)論 0 14
  • 參考資料:菜鳥教程之設(shè)計(jì)模式 設(shè)計(jì)模式概述 設(shè)計(jì)模式(Design pattern)代表了最佳的實(shí)踐,通常被有經(jīng)驗(yàn)...
    Steven1997閱讀 1,272評(píng)論 1 12
  • 前言 關(guān)于設(shè)計(jì)模式六大設(shè)計(jì)原則的資料網(wǎng)上很多,但感覺很多地方解釋地都太過于籠統(tǒng)化,特此再總結(jié)一波。 優(yōu)化第一步-單...
    ghroost閱讀 1,259評(píng)論 0 5
  • 《琉璃瓦》 張愛玲寫得真好,除了老師課上,書上提及的比喻句,我另外挑了自己覺得比較好的五個(gè)比喻。 1.果然,姚先生...
    飛古閱讀 337評(píng)論 4 0
  • 又到了一個(gè)吃橙子的季節(jié),如今的橙子已經(jīng)成為了我們這個(gè)小家庭的生活必需品,可每每看到它,總會(huì)想到十幾年前的那個(gè)冬天。...
    月上芳華閱讀 300評(píng)論 0 0

友情鏈接更多精彩內(nèi)容