開閉原則

来源:https://www.cnblogs.com/liebrother/archive/2019/01/09/10247314.html
-Advertisement-
Play Games

個人博客原文: "開閉原則" 設計模式六大原則之六:開閉原則。 簡介 姓名 :開閉原則 英文名 :Open Closed Principle 價值觀 :老頑童就是我,休想改變我 個人介紹 : Software entities (classes, modules, functions, etc.) ...


個人博客原文:
開閉原則

景

設計模式六大原則之六:開閉原則。

簡介

姓名 :開閉原則

英文名 :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 設計模式》

這周事情比較多,更新會不及時,周五還要出差去一趟上海,周六回深圳,周日回一趟老家,各種奔波。。。

希望文章對您有所幫助,設計模式系列會持續更新,感興趣的同學可以關註公眾號,第一時間獲取文章推送閱讀,也可以一起交流,交個朋友。

公眾號之設計模式系列文章

公眾號


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 在做視頻上傳的時候,發現Not allowed to load local resource,也弄了很久,怕自己忘記了所以記下來 ...
  • 公司項目之前一個需求,需要用戶一進頁面觸摸手機後就自動幫他複製一個串碼。。wtf? 還有這種操作?好吧,需求出來了,那就想實現吧。。。 用戶進來觸摸手機 會產生 三個事件,我們肯定不能直接寫這三個事件去複製,這樣會影響它的預設事件,我們還要做的神不知鬼不覺。。。 所以,在函數內部我們就需要用到下麵代 ...
  • <script src="http://libs.baidu.com/jquery/1.10.2/jquery.min.js"></script> <script> $(document).ready(function(){ //獲得文本框對象 var t = $("#text_box"); //初 ...
  • 經常在項目中需要寫到切換當前欄目的展示效果,定義公共方法 ...
  •  對象拷貝的方法是一個難點,尤其是深拷貝。建議把代碼都運行下,幫助理解拷貝。 一. json方法 1. 適合情況 :  JSON對象的深度克隆。方法是先JSON.stringify() 轉為json字元串, 再JSON.parse() 轉為json數組 2. 缺點:   ...
  • 一、樣本 地址:http://js.zhuamimi.cn/choujiang/index.htm 源碼:https://pan.baidu.com/s/15KhesfcLf1WMOom6PhzCjA 二、實現方法 1:構建環形鏈表 構建環形鏈表主要是為了無限迴圈子節點 環形鏈表數據結構與演算法裡面有 ...
  • 本文由雲+社區發表 作者:paulzeng 導語: Lottie是Airbnb開源的一個面向 iOS、Android、React Native 的動畫庫,可實現非常複雜的動畫,使用也及其簡單,極大釋放人力,值得一試。 一、簡介 Lottie 是Airbnb開源的一個面向 iOS、Android、Re ...
  • 單例模式是軟體工程中最著名的模式之一。從本質上講,單例是一個只允許創建自身的單個實例的類,並且通常可以簡單地訪問該實例。最常見的是,單例不允許在創建實例時指定任何參數——否則對實例的第二個請求但具有不同的參數可能會有問題!(如果對於具有相同參數的所有請求都應訪問相同的實例,則工廠模式更合適。)本文... ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...