個人部落格原文:
開閉原則
設計模式六大原則之六:開閉原則。
簡介
姓名 :開閉原則
英文名 :Open Closed Principle
價值觀 :老頑童就是我,休想改變我
個人介紹 :
Software entities (classes, modules, functions, etc.) should be open for extension, but closed for modification.(軟體中的物件(類,模組,函式等等)應該對於擴充套件是開放的,但是對於修改是封閉的)
(來自維基百科)
停更了三四天了,這幾天比較忙,不僅僅是工作上,更多是精神上。週日突然老胃病又復發了,一直疼到凌晨 4,5 點。因為這次疼得蠻厲害的,所以準備去醫院看一下醫生,這時候才體驗到大城市就醫之苦。週日晚下載了微醫 App (不是做廣告哈),也不知道哪家醫院好,在深圳兩年半還沒去過醫院,隨便選個三甲醫院:北京大學深圳醫院,看了消化內科門診的醫生列表,整整這一週主任醫生都預約滿了,頓時很崩潰,打電話給醫院預約,最快只能預約 17 號,are you kidding?App 上有個 『立即問診』功能,線上把狀況告訴醫生,醫生一天之內接診,需要花 60 塊,我就嘗試一下,沒想到第二天醫生回覆後,說可以下午去醫院看,他可以臨時加號。就這樣跳過了預約,直接看病,不知道你是否也苦於看病煩,可以嘗試這個方法,當然,如果你有更好的方法,可以留言讓更多的人瞭解到。
跑題了跑題了,今天是想和大家分享設計模式最後一個原則:開閉原則。這個原則要求就是允許擴充套件,拒絕修改。既然上面講到看醫生,那就用一個跟看病有關的例子。
故事從這裡開始
小明去醫院看病,醫生開了阿司匹林藥,小明去了收費臺,付了錢,總共 20 塊錢。例子的程式碼如下:
public class OcpTest {
public static void main(String[] args) {
Hospital hospital = new Hospital();
IPatient xiaoMing = new Patient("小明");
hospital.sellMedicine(xiaoMing);
}
}
class Medicine {
private String name;
private BigDecimal price;
public Medicine(String name, BigDecimal price) {
this.name = name;
this.price = price;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public BigDecimal getPrice() {
return price;
}
public void setPrice(BigDecimal price) {
this.price = price;
}
}
class Hospital {
private Medicine medicine = new Medicine("阿司匹林", new BigDecimal(20));
public void sellMedicine(IPatient patient) {
BigDecimal money = patient.pay(medicine);
System.out.println(patient.getName() + " 花了 " + money.setScale(2, BigDecimal.ROUND_UP) + " 塊錢買了藥:" + medicine.getName());
}
}
interface IPatient {
String getName();
BigDecimal pay(Medicine medicine);
}
class Patient implements IPatient{
private String name;
public Patient(String name) {
this.name = name;
}
@Override
public BigDecimal pay(Medicine medicines) {
return medicines.getPrice();
}
@Override
public String getName() {
return name;
}
}
第二天和朋友聚會聊起這事,小紅說道:不對呀,前幾天我在醫院也拿了阿司匹林藥,才 14 塊錢呢。小花說:奇怪了,我買的是 16 塊錢。小杰回應:怎麼我買的是 18 塊。怎麼這藥這麼多個價格。小明 Google 搜了一下,發現價格跟社保有關,幾個人便發現,原來他們都是“不同人”:小明沒有社保,小紅社保是一檔,小花社保是二擋,小杰社保是三擋。(假設社保一檔打 7 折,社保二擋打 8 折,社保三擋打 9 折,虛擬的哈)
發現了這祕密後,作為和 IT 工作相關的人,便討論起醫院系統具體實現是怎麼實現的。小紅說:這很簡單呢,藥品給不同人提供不同的價格。程式碼如下:
class Medicine {
private String name;
private BigDecimal price;
public Medicine(String name, BigDecimal price) {
this.name = name;
this.price = price;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public BigDecimal getPrice() {
return price;
}
public BigDecimal getPrice1() {
return price.multiply(new BigDecimal(0.7));
}
public BigDecimal getPrice2() {
return price.multiply(new BigDecimal(0.8));
}
public BigDecimal getPrice3() {
return price.multiply(new BigDecimal(0.9));
}
public void setPrice(BigDecimal price) {
this.price = price;
}
}
小花說:藥片本身的價格是不會變的,只是給不同人不同價格,所以可以在病人獲取價錢的時候去區分。程式碼如下:
class Patient implements IPatient{
private String name;
private int level;
public Patient(String name) {
this.name = name;
}
@Override
public BigDecimal pay(Medicine medicines) {
if (level == 1) {
return medicines.getPrice().multiply(new BigDecimal(0.7));
} else if (level == 2) {
return medicines.getPrice().multiply(new BigDecimal(0.8));
} else if (level == 3) {
return medicines.getPrice().multiply(new BigDecimal(0.9));
}
return medicines.getPrice();
}
@Override
public String getName() {
return name;
}
}
小杰陷入了沉思。。。
小明發話:你們說的方法都可以實現,但是總感覺不對勁,如果以後有社保四擋,還是要修改原來的程式碼,前 2 天設計模式老師講的開閉原則忘記了麼?裡面說要對擴充套件開放,對修改封閉。我覺得這個藥片價格是因為我們人而變的,那是不是我們可以把沒社保的歸為一類人,一檔社保的也為一類,以此類推。我覺得這樣實現更好,增加多 3 類病人,分別是一檔社保、二擋社保、三擋社保。程式碼如下:
class OneLevelSocialSecurityPatient implements IPatient {
private String name;
public OneLevelSocialSecurityPatient(String name) {
this.name = name;
}
@Override
public BigDecimal pay(Medicine medicine) {
return medicine.getPrice().multiply(new BigDecimal(0.7));
}
@Override
public String getName() {
return this.name;
}
}
class TwoLevelSocialSecurityPatient implements IPatient {
private String name;
public TwoLevelSocialSecurityPatient(String name) {
this.name = name;
}
@Override
public BigDecimal pay(Medicine medicine) {
return medicine.getPrice().multiply(new BigDecimal("0.8"));
}
@Override
public String getName() {
return this.name;
}
}
class ThreeLevelSocialSecurityPatient implements IPatient {
private String name;
public ThreeLevelSocialSecurityPatient(String name) {
this.name = name;
}
@Override
public BigDecimal pay(Medicine medicine) {
return medicine.getPrice().multiply(new BigDecimal("0.9"));
}
@Override
public String getName() {
return this.name;
}
}
// 測試程式碼
public static void main(String[] args) {
Hospital hospital = new Hospital();
IPatient xiaoMing = new Patient("小明");
hospital.sellMedicine(xiaoMing);
IPatient xiaoHong = new OneLevelSocialSecurityPatient("小紅");
hospital.sellMedicine(xiaoHong);
IPatient xiaoHua = new TwoLevelSocialSecurityPatient("小花");
hospital.sellMedicine(xiaoHua);
IPatient xiaoJie = new ThreeLevelSocialSecurityPatient("小杰");
hospital.sellMedicine(xiaoJie);
}
程式碼:
OcpTest.java
看了他們的對話和程式碼,是不是能知道哪種方式更好了?對於小紅來說,她沒理清價格變化的原因,價格變化不在於藥片;小花理清了,但是實現方式差了點,以後如果新增了四擋社保,她的實現要修改原有的程式碼,不符合開閉原則;小明的方法就符合開閉原則,如果新增四擋社保人員,他的方法只需要再額外擴充套件一個四擋社保人員就可以,不用動用其他程式碼。
用了這個大家可能不太喜歡的看病的場景來描述這個開閉原則,不要忌諱哈,希望大家都健健康康,遠離醫院。
總結
重申一下:對擴充套件開放,對修改封閉。如果有同學經常看一些開源框架原始碼就會發現,有很多很多抽象類和介面,debug 進去很繞,其實這些抽象類和介面很多都是為了擴充套件用,因為作為開源框架,不得不實現各種可想象到的方案,而這些都基於開閉原則來實現的。以後有機會也可以寫一下原始碼的文章分享給大家。
參考資料:《大話設計模式》、《Java設計模式》、《設計模式之禪》、《研磨設計模式》、《Head First 設計模式》
這周事情比較多,更新會不及時,週五還要出差去一趟上海,週六回深圳,週日回一趟老家,各種奔波。。。
希望文章對您有所幫助,設計模式系列會持續更新,感興趣的同學可以關注公眾號,第一時間獲取文章推送閱讀,也可以一起交流,交個朋友。