[心得整理] c# 物件導向程式 - 9.OCP 開放封閉原則

[心得整理] c# 物件導向程式 - 9.OCP 開放封閉原則

前言

介紹來到最後一個原則,也是最最最重要的OCP 開放封閉原則
之前學習的五大原則的實踐在寫程式上面
就是達到讓程式可以物件導向的境界

開放封閉原則 - 現實生活面

要用於現實生活中,可以換頭的電動螺絲起子就是最好的範例
開放工具頭可以抽換,封閉主體的馬達架構

開放封閉原則 - 程式面

開放功能可抽換,封閉架構被修改

寫程式的時候最常遇到功能修改
原本要A改B換C,上線後又說還是要A
但老闆覺得C也不錯,可以後台做的開關讓user切換嗎之類的
(想像一下原本螺絲起子情況 本來說要十字,後來改要一字,最後說能不能兩個都要)
如果功能是可以被抽換的這樣是不是就比較不會令人討厭呢

用購物車運費的案例來說好了 
參考91的​[30天快速上手TDD][Day 9]Refactoring legacy code 簡介
各貨運公司的運費都不一樣,如何把算運費的功能設計為可抽換
先來看一下最直覺的寫法

            int shippingFee = 0;
            int piecesRate = 0;
            if (shippingComapanyName == "黑貓")
            {
                piecesRate = 150;
                shippingFee = pieces * piecesRate;
            }
            else if (shippingComapanyName == "白貓")
            {
                piecesRate = 130;
                shippingFee = pieces * piecesRate;
                if (shippingFee > 500)
                {
                    shippingFee -= 50;
                }
            }

這樣個程式碼就要不停的維護這if
這時候應該把之前介紹的原則拿出來運用

SRP 單一職責
算運費應該是每間貨運公司都不同
應該有貨運公司有自己算運費的規則
要有貨物公司 class 

DIP 依賴反轉
要有介面(抽象) 這樣才能抽換貨運公司
建立一個貨運公司介面

ISP 介面隔離
算運費是每間貨運都會有的功能
所以要在貨運公司介面
設計一個算運費功能

當然在開發過程也要注意
LSP 里氏替換 -  繼承的時候要考慮到子類別要可以替代父類別
LKP 最少知識 - 不要把流程複雜化

來看一下程式碼

    //介面
    public interface IShippable
    {
        void FeeCalculate(int pieces);
        int GetFee();
    }

    //白貓貨運公司
    public class WhiteCat:IShippable
    {
        private int _shippingFee;
        public void FeeCalculate(int pieces)
        {
            int piecesRate = 130;
            this._shippingFee = pieces * piecesRate;
            if (this._shippingFee > 500)
            {
                this._shippingFee -= 50;
            }
        }
        public int GetFee()
        {
            return this._shippingFee;
        }
    }

黑貓貨運的class 請自己腦補一下
目前我們有了介面和class 
在取運費的時候要如何能夠把IF拿掉呢
還記得DIP有提到IoC嗎,我們應該讓別人幫我new 貨運公司
就像這樣

    public class ShippingCompanyFactory
    {
        public static IShippable GetShippingCompany(string shippingCompanyName)
        {
            switch (shippingCompanyName)
            {
                case "BlackCat":
                    return new BlackCat();
                case "WhiteCat":
                    return new WhiteCat();
            }
        }
    }

最後來看一下主程式

IShippable shippingCompany =
           ShippingCompanyFactory.GetShippingCompany("白貓");
shippingCompany.FeeCalculate(pieces);
shippingFee = shippingCompany.GetFee();

之後要加入新貨運公司的時候,就去新增貨物公司的class
再去 ShippingCompanyFactory 加入
基本上主流程式是不需要改變的

對我來說 
OCP 開放封閉原則,就是要讓程式說話
主要流程不應該出現細節與邏輯

補充

實作上 原本寫死的變數  變成用參數傳進來 這樣也算是OCP的一種 現在不用改這class的程式碼 只要改變呼叫端的變數 對吧
public void Exec() { Console.WriteLine("FixString")  } => public void Exec(string msg) { Console.WriteLine(msg)  }

繼承也可 父類別virtual 子類別在override 不過 這樣很容易 改太大或是改成完全不相關 不是很建議

結語

終於把OCP寫完了,SOLID原則還沒結束,還會有一篇整理更多網路上的資源

推薦閱讀

[ASP.NET]91之ASP.NET由淺入深 不負責講座 Day18 - 開放封閉原則

亂談軟體設計(2):Open-Closed Principle

如果內容有誤請多鞭策謝謝