開閉原則
定義:
一個軟件實體如類,模塊和函數應該對擴展開放,對修改關閉。
什麽是開閉原則
開閉原則明確的告訴我們:軟件實現應該對擴展開放,對修改關閉,其含義是說一個軟件實體應該通過擴展來實現變化,而不是通過修改已有的代碼來實現變化的。那什麽是軟件實體呢?軟件實體包括以下幾個部分:
- 項目或軟件產品中按照一定的邏輯規則劃分的模塊
- 抽象和類
- 方法
一個軟件產品只要在生命周期內,都會發生變化,即然變化是一個事實,我們就應該在設計時盡量適應這些變化,以提高項目的穩定性和靈活性,真正實現“擁抱變化”。開閉原則告訴我們應盡量通過擴展軟件實體的行為來實現變化,而不是通過修改現有代碼來完成變化,它是為軟件實體的未來事件而制定的對現行開發設計進行約束的一個原則。
我們舉例說明什麽是開閉原則,以書店銷售書籍為例,其類圖如下:
書籍接口:
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,intView Codeprice,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()+"書籍價格:"+novel.getPrice()); } }
項目投產生,書籍正常銷售,但是我們經常因為各種原因,要打折來銷售書籍,這是一個變化,我們要如何應對這樣一個需求變化呢?
我們有下面三種方法可以解決此問題:
- 修改接口
-
在IBook接口中,增加一個方法getOffPrice(),專門用於進行打折處理,所有的實現類實現此方法。但是這樣的一個修改方式,實現類NovelBook要修改,同時IBook接口應該是穩定且可靠,不應該經常發生改變,否則接口作為契約的作用就失去了。因此,此方案否定。
-
修改實現類
修改NovelBook類的方法,直接在getPrice()方法中實現打折處理。此方法是有問題的,例如我們如果getPrice()方法中只需要讀取書籍的打折前的價格呢?這不是有問題嗎?當然我們也可以再增加getOffPrice()方法,這也是可以實現其需求,但是這就有二個讀取價格的方法,因此,該方案也不是一個最優方案。 -
通過擴展實現變化
我們可以增加一個子類OffNovelBook,覆寫getPrice方法。此方法修改少,對現有的代碼沒有影響,風險少,是個好辦法。下面是修改後的類圖:
打折類:
public class OffNovelBook implements NovelBook{ public OffNovelBook(String name,int price,String author){ super(name,price,author); } //覆寫價格方法,當價格大於40,就打8析,其他價格就打9析 public int getPrice(){ if(this.price > 40){ return this.price * 0.8; }else{ return this.price * 0.9; } } }
現在打折銷售開發完成了,我們只是增加了一個OffNovelBook類,我們修改的代碼都是高層次的模塊,沒有修改底層模塊,代碼改變量少,可以有效的防止風險的擴散。
我們可以把變化歸納為二種類型:
-
邏輯變化
只變化了一個邏輯,而不涉及其他模塊,比如一個算法是a*b*c,現在需要修改為a+b+c,可以直接通過修改原有類中的方法的方式來完成,前提條件是所有依賴或關聯類都按照相同的邏輯處理 -
子模塊變化
一人模塊變化,會對其它的模塊產生影響,特別是一個低層次的模塊變化必然引起高層模塊的變化,因此在通過擴展完成變化。 -
如何使用開閉原則
第一:抽象約束
抽象是對一組事物的通用描述,沒有具體的實現,也就表示它可以有非常多的可能性,可以跟隨需求的變化而變化。因此,通過接口或抽象類可以約束一組可能變化的行為,並且能夠實現對擴展開放,其包含三層含義: - 通過接口或抽象類約束擴散,對擴展進行邊界限定,不允許出現在接口或抽象類中不存在的public方法。
- 參數類型,引用對象盡量使用接口或抽象類,而不是實現類,這主要是實現裏氏替換原則的一個要求
- 抽象層盡量保持穩定,一旦確定就不要修改
- 第二:元數據(metadata)控件模塊行為
編程是一個很苦很累的活,那怎麽才能減輕壓力呢?答案是盡量使用元數據來控制程序的行為,減少重復開發。什麽是元數據?用來描述環境和數據的數據,通俗的說就是配置參數,參數可以從文件中獲得,也可以從數據庫中獲得。 -
第三:制定項目章程
在一個團隊中,建立項目章程是非常重要的,因為章程是所有人員都必須遵守的約定,對項目來說,約定優於配置。這比通過接口或抽象類進行約束效率更高,而擴展性一點也沒有減少。第四:封裝變化
對變化封裝包含兩層含義:
(1)將相同的變化封裝到一個接口或抽象類中
(2)將不同的變化封裝到不同的接口或抽象類中,不應該有兩個不同的變化出現在同一個接口或抽象類中。
封裝變化,也就是受保護的變化,找出預計有變化或不穩定的點,我們為這些變化點創建穩定的接口。
開閉原則