開閉原則(Open-Closed Principle, OCP)是指一個軟件實體如類、模塊和函數應該對擴展開放,對修改關閉。
強調的是用抽象構建框架,用實現擴展細節。
開閉原則,是面向對象設計中最基礎的設計原則。它指導咱們如何創建穩定靈活的系統。
例如:咱們版本更新,儘量不修改原代碼,可是能夠增長新功能。框架
開閉原則要求咱們經過保持原有代碼不變,添加新代碼來實現軟件的變化,由於不涉及原代碼的改動,這樣能夠避免爲實現新功能而改壞線上功能的狀況,避免老用戶的流失。ide
軟件開發規範性好的團隊都會寫單元測試,若是原有的某個功能發生了變化,則單元測試代碼也應作相應的變動,不然就有可能致使測試出錯。若是每次軟件的變化,除了變動功能代碼以外,還得變動測試代碼,書寫測試代碼一樣須要消耗工時,這樣在項目中引入單元測試就成了累贅。開閉原則可讓單元測試充分發揮做用而又不會成爲後期軟件開發的累贅。模塊化
開閉原則可讓代碼中的各功能,以及新舊功能獨立存在於不一樣的單元模塊中,一旦某個功能出現問題,能夠很快地鎖定代碼位置做出修改,因爲模塊間代碼獨立不相互調用,更改一個功能的代碼也不會引發其餘功能的崩潰。函數
在項目開發過程當中,有時候閱讀前人的代碼是件很頭疼的事,尤爲項目開發週期比較長,可能三五年,再加上公司人員流動性大,原有代碼的開發人員早就另謀高就,而代碼寫的更是一團糟,自帶混淆,能走彎路不走直路。而如今須要在原有功能的基礎上開發新功能,若是開閉原則使用得當的話,咱們是不須要看懂原有代碼實現細節即可以添加新代碼實現新功能,畢竟有時候閱讀一個功能的代碼,比本身從新實現這個功能用的時間還要長。單元測試
假設咱們使用出售電腦爲例,首選定義一個頂層接口Computer:測試
/** * Create By Ke Shuiqiang 2020/3/4 17:12 * 頂層接口,定義了獲取電腦信息的接口方法 */ public interface Computer { double getPrice();//價格 String getColor();//顏色 int getMemory();//內存 float getSize();//尺寸 }
而後定義兩個實現類,華碩電腦與蘋果Macui
/** * Create By Ke Shuiqiang 2020/3/4 17:20 * 華碩 */ public class AsusComputer implements Computer { private double price; private String color; private int memory; private float size; public AsusComputer(double price, String color, int memory, float size) { this.price = price; this.color = color; this.memory = memory; this.size = size; } @Override public double getPrice() { return this.price; } @Override public String getColor() { return this.color; } @Override public int getMemory() { return this.memory; } @Override public float getSize() { return this.size; } }
/** * Create By Ke Shuiqiang 2020/3/4 17:17 * Mac */ public class MacComputer implements Computer{ private double price; private String color; private int memory; private float size; public MacComputer(double price, String color, int memory, float size) { this.price = price; this.color = color; this.memory = memory; this.size = size; } @Override public double getPrice() { return this.price; } @Override public String getColor() { return this.color; } @Override public int getMemory() { return this.memory; } @Override public float getSize() { return this.size; } }
測試類:this
public class Test { public static void main(String\[\] args) { Computer computer = new AsusComputer(4888.88D,"深藍",8,14.0F); System.out.println( "電腦:華碩\n" + "售價:" + computer.getPrice() + "\n" + "顏色:" + computer.getColor() + "\n" + "內存:" + computer.getMemory() + "\n" + "尺寸:" + computer.getSize() ); } }
電腦:華碩 售價:4888.88 顏色:深藍 內存:8 尺寸:14.0
這是咱們一開始的需求,可是隨着軟件發佈運行,咱們需求不可能一成不變,確定要接軌市場。假設如今是雙十一,須要搞促銷活動。那麼咱們的代碼確定要添加新的功能。
可能有些剛入職的新人會在原有的代碼上作改動:spa
@Override public double getPrice() { return this.price * 0.6; }
這確定不符合咱們的開閉原則,雖然看起來這樣作最直接,也最簡單,可是絕大部分項目中,一個功能的實現遠比想像要複雜的多,咱們在原有的代碼中進行修改,其風險遠比擴展和實現一個方法要大的多。
正確的作法能夠這樣:設計
/** * Create By Ke Shuiqiang 2020/3/4 17:50 * 華碩電腦打折 */ public class AsusDiscountComputer extends AsusComputer { private float discount; public AsusDiscountComputer(double price, String color, int memory, float size, float discount) { super(price, color, memory, size); this.discount = discount; } public double getDiscountPrice(){ return getPrice() * this.discount; } }
實現一個關於折扣的子類,其中包含一個關於折扣的方法,這方法至關於一個擴展方法。能夠看到這個子類是AsusComputer的,那爲何不把他設計成一個共用的折扣類呢,好比DiscountComputer,全部實現類都繼承這個折扣類。這是由於每種實現類的折扣方案多是不同的。因此咱們最好能把它做爲每一個實現類的子類單獨實現。若是你能確保你的業務中的新功能能兼容全部相關聯的需求你也能夠共用一個。
public class Test1 { public static void main(String[] args) { Computer computer = new AsusDiscountComputer(4888.88D,"深藍",8,14.0F,0.5F); AsusDiscountComputer asusDiscountComputer = (AsusDiscountComputer)computer; System.out.println( "電腦:華碩\n" + "原價:" + asusDiscountComputer.getPrice() + "\n" + "售價:" + asusDiscountComputer.getDiscountPrice() + "\n" + "顏色:" + asusDiscountComputer.getColor() + "\n" + "內存:" + asusDiscountComputer.getMemory() + "\n" + "尺寸:" + asusDiscountComputer.getSize() ); } }
電腦:華碩 原價:4888.88 售價:2444.44 顏色:深藍 內存:8 尺寸:14.0
你能夠看到若是想要調用getDiscountPrice()方法,在原有的基礎上你還要對它進行強轉,若是你能肯定新擴展的需求,能兼容原有的繼承體系,你也能夠把它抽取到頂層的Computer的接口中。
最後看一下繼承體系
上面只是一個很簡單的示例,在實際開發過程當中,並非必定要求全部代碼都遵循設計原則,咱們要考慮人力、時間、成本、質量,不是刻意追求完美,要在適當的場景遵循設計原則,體現的是一種平衡取捨,幫助咱們設計出更加優雅的代碼結構。