03.開閉原則詳細(xì)介紹

03.開閉原則詳細(xì)介紹

目錄介紹

  • 01.問題思考的分析
  • 02.如何理解開閉原則
  • 03.開閉原則的背景
  • 04.開閉原則比較難學(xué)
  • 05.實(shí)現(xiàn)開閉原則方式
  • 06.畫圖形案例分析
  • 07.銀行業(yè)務(wù)案例分析
  • 08.開閉原則優(yōu)缺點(diǎn)
  • 09.開閉原則的總結(jié)

推薦一個(gè)好玩網(wǎng)站

一個(gè)最純粹的技術(shù)分享網(wǎng)站,打造精品技術(shù)編程專欄!編程進(jìn)階網(wǎng)

https://yccoding.com/

設(shè)計(jì)模式Git項(xiàng)目地址:https://github.com/yangchong211/YCDesignBlog

單一職責(zé)原則(SRP)是面向?qū)ο笤O(shè)計(jì)的重要原則,強(qiáng)調(diào)一個(gè)類或模塊應(yīng)僅負(fù)責(zé)完成一個(gè)特定的職責(zé)或功能。通過將復(fù)雜的功能分解為多個(gè)粒度小、功能單一的類,可以提高系統(tǒng)的靈活性、可維護(hù)性和可擴(kuò)展性。

本文詳細(xì)介紹了如何理解單一職責(zé)原則,包括方法、接口和類層面的應(yīng)用,并通過具體例子解釋了其優(yōu)勢(shì)和判斷標(biāo)準(zhǔn)。此外,還探討了在實(shí)際開發(fā)中如何平衡類的設(shè)計(jì),避免過度拆分導(dǎo)致的復(fù)雜性增加。

01.問題思考的分析

  1. 什么叫作開閉原則,他的主要用途是什么?
  2. 如何做到拓展開放,修改封閉這一準(zhǔn)則,結(jié)合案例說(shuō)一下如何實(shí)現(xiàn)?
  3. 你平常是如何理解開閉原則的,判斷的標(biāo)準(zhǔn)是什么?

02.如何理解開閉原則

開閉原則的英文全稱是 Open Closed Principle,簡(jiǎn)寫為 OCP。它的英文描述是:software entities (modules, classes, functions, etc.) should be open for extension , but closed for modification。

我們把它翻譯成中文就是:軟件實(shí)體(模塊、類、方法等)應(yīng)該“對(duì)擴(kuò)展開放、對(duì)修改關(guān)閉”。

這個(gè)描述比較簡(jiǎn)略,如果我們?cè)敿?xì)表述一下,那就是,添加一個(gè)新的功能應(yīng)該是,在已有代碼基礎(chǔ)上擴(kuò)展代碼(新增模塊、類、方法等),而非修改已有代碼(修改模塊、類、方法等)。

03.開閉原則的背景

在軟件的生命周期內(nèi),因?yàn)樽兓?、升?jí)和維護(hù)等原因需要對(duì)軟件原有代碼進(jìn)行修改時(shí),可能會(huì)將錯(cuò)誤引入原本已經(jīng)經(jīng)過測(cè)試的舊代碼中,破壞原有系統(tǒng)。

因此,當(dāng)軟件需要變化時(shí),我們應(yīng)該盡量通過擴(kuò)展的方式來(lái)實(shí)現(xiàn)變化,而不是通過修改已有的代碼來(lái)實(shí)現(xiàn)。

當(dāng)然,在現(xiàn)實(shí)開發(fā)中,只通過繼承的方式來(lái)升級(jí)、維護(hù)原有系統(tǒng)只是一個(gè)理想化的愿景,因此,在實(shí)際的開發(fā)過程中,修改原有代碼、擴(kuò)展代碼往往是同時(shí)存在的。

軟件開發(fā)過程中,最不會(huì)變化的就是變化本身。產(chǎn)品需要不斷地升級(jí)、維護(hù),沒有一個(gè)產(chǎn)品從第一版本開發(fā)完就再?zèng)]有變化了,除非在下個(gè)版本誕生之前它已經(jīng)被終止。

產(chǎn)品需要升級(jí),修改原來(lái)的代碼就可能會(huì)引發(fā)其他的問題。那么如何確保原有軟件模塊的正確性,以及盡量少地影響原有模塊,答案就是盡量遵守本章要講述的開閉原則。

04.開閉原則比較難學(xué)

個(gè)人覺得,開閉原則是 SOLID 中最難理解、最難掌握,同時(shí)也是最有用的一條原則。

之所以說(shuō)這條原則難理解,那是因?yàn)?,“怎樣的代碼改動(dòng)才被定義為‘?dāng)U展’?怎樣的代碼改動(dòng)才被定義為‘修改’?怎么才算滿足或違反‘開閉原則’?修改代碼就一定意味著違反‘開閉原則’嗎?”等等這些問題,都比較難理解。

之所以說(shuō)這條原則難掌握,那是因?yàn)椋叭绾巫龅健畬?duì)擴(kuò)展開放、修改關(guān)閉’?如何在項(xiàng)目中靈活地應(yīng)用‘開閉原則’,以避免在追求擴(kuò)展性的同時(shí)影響到代碼的可讀性?”等等這些問題,都比較難掌握。

之所以說(shuō)這條原則最有用,那是因?yàn)椋瑪U(kuò)展性是代碼質(zhì)量最重要的衡量標(biāo)準(zhǔn)之一。在 23 種經(jīng)典設(shè)計(jì)模式中,大部分設(shè)計(jì)模式都是為了解決代碼的擴(kuò)展性問題而存在的,主要遵從的設(shè)計(jì)原則就是開閉原則。

05.實(shí)現(xiàn)開閉原則方式

為了實(shí)現(xiàn)開閉原則,常用的設(shè)計(jì)技術(shù)有以下幾種:

  1. 抽象類和接口:通過定義抽象類和接口來(lái)約定行為,然后通過繼承和實(shí)現(xiàn)這些抽象類和接口來(lái)擴(kuò)展功能。
  2. 策略模式:將算法的實(shí)現(xiàn)分離到不同的類中,通過組合方式來(lái)實(shí)現(xiàn)不同的行為。
  3. 裝飾器模式:通過對(duì)對(duì)象進(jìn)行包裝,動(dòng)態(tài)地添加新的行為或功能。

06.畫圖形案例分析

6.1 普通案例實(shí)現(xiàn)

假設(shè)有一個(gè)圖形繪制程序,程序需要能夠繪制不同形狀的圖形,比如矩形、圓形和三角形。最初的設(shè)計(jì)可能會(huì)像這樣:

public class GraphicEditor {

    public void draw(Shape shape) {
        if (shape.m_type == 1) {
            drawRectangle();
        } else if(shape.m_type == 2) {
            drawCircle();
        }
    }

    public void drawRectangle() {
        System.out.println("畫長(zhǎng)方形");
    }

    public void drawCircle() {
        System.out.println("畫圓形");
    }

    class Shape {
        int m_type;
    }

    class Rectangle extends Shape {
        Rectangle() {
            super.m_type=1;
        }
    }

    class Circle extends Shape {
        Circle() {
            super.m_type=2;
        }
    }
}

我們來(lái)看看,這個(gè)代碼,初看是符合要求了,再想想,要是我增加一種形狀呢? 比如增加三角形.

  1. 首先,要增加一個(gè)三角形的類, 繼承自Shape
  2. 第二,要增加一個(gè)畫三角形的方法drawTriage()
  3. 第三,在draw方法中增加一種類型type=3的處理方案

在這個(gè)設(shè)計(jì)中,每當(dāng)我們需要添加新的圖形類型,就需要修改 GraphicEditor 類,添加新的 if 條件。我們需要修改已有的代碼來(lái)實(shí)現(xiàn)新功能。

這就違背了開閉原則-對(duì)擴(kuò)展開發(fā),對(duì)修改關(guān)閉。增加一個(gè)類型,修改了三處代碼。

6.2 開閉原則演變

為了符合開閉原則,我們可以進(jìn)行重構(gòu)。首先,我們定義一個(gè)抽象類Shape

public class GraphicEditor {

    public void draw(Shape shape) {
        shape.draw();
    }
    
    interface Shape {
        void draw();
    }

    class Rectangle implements Shape {

        @Override
        public void draw() {
            System.out.println("畫矩形");
        }
    }

    class Circle implements Shape {

        @Override
        public void draw() {
            System.out.println("畫圓形");
        }
    }

}

各種類型的形狀自己規(guī)范自己的行為, 而GraphicEditor.draw()只負(fù)責(zé)畫出來(lái). 當(dāng)增加一種類型三角形. 只需要

  1. 第一,增加一個(gè)三角形的類,實(shí)現(xiàn)Shape接口
  2. 第二,調(diào)用draw方法,劃出來(lái)就可以了

整個(gè)過程都是在擴(kuò)展,而沒有修改原來(lái)的類。這個(gè)設(shè)計(jì)是符合開閉原則的。

6.3 使用例子分析

讓我們來(lái)看一個(gè)具體的使用例子,展示如何遵循開閉原則來(lái)進(jìn)行擴(kuò)展。

public class Main {
    public static void main(String[] args) {
        Shape circle = new Circle();
        Shape rectangle = new Rectangle();
        GraphicEditor editor = new GraphicEditor();

        editor.drawShape(circle);
        editor.drawShape(rectangle);
    }
}

在這個(gè)例子中,我們創(chuàng)建了一個(gè)圓形對(duì)象和一個(gè)矩形對(duì)象,并通過 GraphicEditor 類來(lái)繪制它們。當(dāng)我們需要添加新的圖形類型(例如三角形)時(shí),只需創(chuàng)建一個(gè)新的類實(shí)現(xiàn) Shape 接口,而不需要修改現(xiàn)有的代碼:

// 三角形類
public class Triangle implements Shape {
    @Override
    public void draw() {
        // 繪制三角形的代碼
    }
}

// 使用新的三角形類
public class Main {
    public static void main(String[] args) {
        Shape circle = new Circle();
        Shape rectangle = new Rectangle();
        Shape triangle = new Triangle();
        GraphicEditor editor = new GraphicEditor();

        editor.drawShape(circle);
        editor.drawShape(rectangle);
        editor.drawShape(triangle);
    }
}

通過這種方式,我們可以在不修改 GraphicEditor 類的情況下,輕松地?cái)U(kuò)展新的圖形類型,真正實(shí)現(xiàn)了對(duì)擴(kuò)展開放,對(duì)修改關(guān)閉的設(shè)計(jì)原則。

07.銀行業(yè)務(wù)案例分析

7.1 業(yè)務(wù)基礎(chǔ)實(shí)現(xiàn)

比如現(xiàn)在有一個(gè)銀行業(yè)務(wù),存錢,取錢和轉(zhuǎn)賬。最初我們會(huì)怎么思考呢?

  1. 首先有一個(gè)銀行業(yè)務(wù)類, 用來(lái)處理銀行的業(yè)務(wù)
  2. 銀行有哪些業(yè)務(wù)呢? 存錢,取錢,轉(zhuǎn)賬, 這都是銀行要執(zhí)行的操作
  3. 那外部說(shuō)我要存錢, 我要取錢,我要轉(zhuǎn)賬, 通過一個(gè)類型來(lái)告訴我們
public class BankBusiness {

   public void operate(int type) {
       if (type == 1) {
           save();
       } else if(type == 2) {
           take();
       } else if(type == 3) {
           transfer();
       }
   }

   public void save(){
       System.out.println("存錢");
   }

   public void take(){
       System.out.println("取錢");
   }

   public void transfer() {
       System.out.println("轉(zhuǎn)賬");
   }
}

咋一看已經(jīng)實(shí)現(xiàn)了需求. 但是現(xiàn)在有新的需求來(lái)了, 銀行要增加功能---理財(cái). 理財(cái)是銀行業(yè)務(wù)的一種, 自然是新增一個(gè)方法.

然后在operate()方法里增加一種類型. 這就是一個(gè)糟糕的設(shè)計(jì), 增加新功能, 但是卻修改了原來(lái)的代碼

7.2 開閉原則演變

設(shè)計(jì)成接口抽象的形式,利用開關(guān)原則,可以嘗試改造為下面的代碼。將不同對(duì)象分類的服務(wù)方法進(jìn)行抽象,把業(yè)務(wù)邏輯的緊耦合關(guān)系拆開,實(shí)現(xiàn)代碼的隔離保證了方便的擴(kuò)展。

public interface Business {
    public void operate();
}

public class Save implements Business {
    @Override
    public void operate() {
        System.out.println("存錢業(yè)務(wù)");
    }
}

public class Take implements Business {
    @Override
    public void operate() {
        System.out.println("取錢業(yè)務(wù)");
    }
}

public class Transfer implements Business {
    @Override
    public void operate() {
        System.out.println("轉(zhuǎn)賬業(yè)務(wù)");
    }
}

public class BankBusinesses {
    /**
     * 處理銀行業(yè)務(wù)
     * @param business
     */
    public void operate(Business business) {
        System.out.println("處理銀行業(yè)務(wù)");
        business.operate();
    }
}

通過接口抽象的形式方便擴(kuò)展, 加入要新增理財(cái)功能. 只需新增一個(gè)理財(cái)類, 其他業(yè)務(wù)代碼都不需要修改.

其實(shí), 在日常工作中, 經(jīng)常會(huì)遇到這種情況. 因?yàn)槲覀兤綍r(shí)寫業(yè)務(wù)邏輯會(huì)更多一些, 而業(yè)務(wù)就像流水賬, 今天一個(gè)明天一個(gè)一點(diǎn)一點(diǎn)的增加. 所以,當(dāng)業(yè)務(wù)增加到3個(gè)的時(shí)候, 我們就要思考, 如何寫能夠方便擴(kuò)展

08.開閉原則優(yōu)缺點(diǎn)

8.1 開閉原則的優(yōu)點(diǎn)

  1. 提高了代碼的可維護(hù)性與復(fù)用性:遵循開閉原則可以讓代碼更加穩(wěn)定和可維護(hù),同時(shí)也使得代碼更容易被復(fù)用。如果我們需要修改某個(gè)模塊的行為,只需要擴(kuò)展該模塊而不需要直接修改源代碼,這樣就不會(huì)影響到其他的模塊。
  2. 提高了代碼的可擴(kuò)展性:開閉原則還可以提高代碼的可擴(kuò)展性。通過擴(kuò)展已有的代碼,我們可以很容易地添加新的功能或改進(jìn)現(xiàn)有功能,從而適應(yīng)業(yè)務(wù)需求的更改。
  3. 提高了代碼的可測(cè)試性:遵循開閉原則可以降低代碼的耦合度,使得測(cè)試更加容易。因?yàn)槲覀冎恍枰獪y(cè)試新增的代碼,而不必驗(yàn)證已有代碼的正確性。

8.2 開閉原則的缺點(diǎn)

  1. 對(duì)代碼的設(shè)計(jì)要求高:遵循開閉原則需要對(duì)代碼進(jìn)行良好的抽象和封裝,這對(duì)程序員的設(shè)計(jì)能力和經(jīng)驗(yàn)要求較高。如果設(shè)計(jì)不好,可能會(huì)導(dǎo)致代碼過于復(fù)雜難以維護(hù)。
  2. 可能會(huì)增加代碼量:通過擴(kuò)展已有的代碼來(lái)實(shí)現(xiàn)新功能,可能會(huì)增加代碼量,使得系統(tǒng)變得更加復(fù)雜。這需要我們?cè)谄胶饪删S護(hù)性和代碼量之間做出權(quán)衡。
  3. 可能會(huì)帶來(lái)設(shè)計(jì)上的限制:在某些情況下,為了遵循開閉原則,我們可能需要引入更多的抽象層或接口。這可能會(huì)帶來(lái)一定的設(shè)計(jì)上的限制,限制了代碼的表達(dá)能力和靈活性。

09.開閉原則的總結(jié)

9.1 理解開閉原則

如何理解“對(duì)擴(kuò)展開放、對(duì)修改關(guān)閉”?

添加一個(gè)新的功能,應(yīng)該是通過在已有代碼基礎(chǔ)上擴(kuò)展代碼(新增模塊、類、方法、屬性等),而非修改已有代碼(修改模塊、類、方法、屬性等)的方式來(lái)完成。

關(guān)于定義,我們有兩點(diǎn)要注意。

  1. 第一點(diǎn)是,開閉原則并不是說(shuō)完全杜絕修改,而是以最小的修改代碼的代價(jià)來(lái)完成新功能的開發(fā)。
  2. 第二點(diǎn)是,同樣的代碼改動(dòng),在粗代碼粒度下,可能被認(rèn)定為“修改”;在細(xì)代碼粒度下,可能又被認(rèn)定為“擴(kuò)展”。

9.2 如何做到開閉原則

如何做到“對(duì)擴(kuò)展開放、修改關(guān)閉”?

我們要時(shí)刻具備擴(kuò)展意識(shí)、抽象意識(shí)、封裝意識(shí)。在寫代碼的時(shí)候,我們要多花點(diǎn)時(shí)間思考一下,這段代碼未來(lái)可能有哪些需求變更,如何設(shè)計(jì)代碼結(jié)構(gòu),事先留好擴(kuò)展點(diǎn),以便在未來(lái)需求變更的時(shí)候,在不改動(dòng)代碼整體結(jié)構(gòu)、做到最小代碼改動(dòng)的情況下,將新的代碼靈活地插入到擴(kuò)展點(diǎn)上。

學(xué)習(xí)設(shè)計(jì)原則,要多問個(gè)為什么。

不能把設(shè)計(jì)原則當(dāng)真理,而是要理解設(shè)計(jì)原則背后的思想。搞清楚這個(gè),比單純理解原則講的是啥,更能讓你靈活應(yīng)用原則。

9.3 開閉原則的總結(jié)

  1. 問題思考:開閉原則的主要用途是什么?如何才能做到對(duì)外拓展開放,對(duì)內(nèi)修改關(guān)閉?你平常是如何理解開閉原則的,判斷的標(biāo)準(zhǔn)是什么?
  2. 如何理解開閉原則:軟件實(shí)體(模塊、類、方法等)應(yīng)該“對(duì)擴(kuò)展開放、對(duì)修改關(guān)閉”。
  3. 為何開閉原則比較難學(xué):怎樣的代碼改動(dòng)才被定義為‘修改’?怎么才算滿足或違反‘開閉原則’?如何理解大部分設(shè)計(jì)模式都是遵循開閉原則!
  4. 開筆原則的背景:軟件/業(yè)務(wù)迭代升級(jí)中,面對(duì)代碼變化,修改原來(lái)代碼可能引入原有模塊bug風(fēng)險(xiǎn),因此盡量通過對(duì)外拓展來(lái)實(shí)現(xiàn)功能迭代。
  5. 實(shí)現(xiàn)開閉原則的方式:常用的設(shè)計(jì)技術(shù)有以下幾種,1.抽象類和接口;2.策略模式;3.裝飾器模式等。
  6. 開閉原則的案例教學(xué):繪制圓形/矩形,通過if-else來(lái)執(zhí)行不同case邏輯,新增一種類型則需要修改原邏輯。因此考慮通過接口+抽象類方式實(shí)現(xiàn)友好拓展。
  7. 開閉原則的優(yōu)點(diǎn):1.提高代碼拓展性和可維護(hù)性;2.提高代碼可測(cè)試的粒度;3.降低的代碼耦合度。
  8. 開閉原則的缺點(diǎn):1.對(duì)代碼設(shè)計(jì)要求高;2.可能會(huì)導(dǎo)致代碼量增大和變得復(fù)雜;3.可能會(huì)帶來(lái)設(shè)計(jì)上的限制。
  9. 總結(jié)如何理解開閉原則:當(dāng)需求發(fā)生變化時(shí),我們應(yīng)該通過添加新的代碼來(lái)擴(kuò)展功能,而不是修改已有的代碼。
  10. 總結(jié)如何運(yùn)用開閉原則:通過封裝變化、使用抽象化、利用擴(kuò)展點(diǎn)和遵循依賴倒置原則來(lái)實(shí)現(xiàn)。

10.更多內(nèi)容推薦

模塊 描述 備注
GitHub 多個(gè)YC系列開源項(xiàng)目,包含Android組件庫(kù),以及多個(gè)案例 GitHub
博客匯總 匯聚Java,Android,C/C++,網(wǎng)絡(luò)協(xié)議,算法,編程總結(jié)等 YCBlogs
設(shè)計(jì)模式 六大設(shè)計(jì)原則,23種設(shè)計(jì)模式,設(shè)計(jì)模式案例,面向?qū)ο笏枷?/td> 設(shè)計(jì)模式
Java進(jìn)階 數(shù)據(jù)設(shè)計(jì)和原理,面向?qū)ο蠛诵乃枷耄琁O,異常,線程和并發(fā),JVM Java高級(jí)
網(wǎng)絡(luò)協(xié)議 網(wǎng)絡(luò)實(shí)際案例,網(wǎng)絡(luò)原理和分層,Https,網(wǎng)絡(luò)請(qǐng)求,故障排查 網(wǎng)絡(luò)協(xié)議
計(jì)算機(jī)原理 計(jì)算機(jī)組成結(jié)構(gòu),框架,存儲(chǔ)器,CPU設(shè)計(jì),內(nèi)存設(shè)計(jì),指令編程原理,異常處理機(jī)制,IO操作和原理 計(jì)算機(jī)基礎(chǔ)
學(xué)習(xí)C編程 C語(yǔ)言入門級(jí)別系統(tǒng)全面的學(xué)習(xí)教程,學(xué)習(xí)三到四個(gè)綜合案例 C編程
C++編程 C++語(yǔ)言入門級(jí)別系統(tǒng)全面的教學(xué)教程,并發(fā)編程,核心原理 C++編程
算法實(shí)踐 專欄,數(shù)組,鏈表,棧,隊(duì)列,樹,哈希,遞歸,查找,排序等 Leetcode
Android 基礎(chǔ)入門,開源庫(kù)解讀,性能優(yōu)化,F(xiàn)ramework,方案設(shè)計(jì) Android

23種設(shè)計(jì)模式

23種設(shè)計(jì)模式 & 描述 & 核心作用 包括
創(chuàng)建型模式
提供創(chuàng)建對(duì)象用例。能夠?qū)④浖K中對(duì)象的創(chuàng)建和對(duì)象的使用分離
工廠模式(Factory Pattern)
抽象工廠模式(Abstract Factory Pattern)
單例模式(Singleton Pattern)
建造者模式(Builder Pattern)
原型模式(Prototype Pattern)
結(jié)構(gòu)型模式
關(guān)注類和對(duì)象的組合。描述如何將類或者對(duì)象結(jié)合在一起形成更大的結(jié)構(gòu)
適配器模式(Adapter Pattern)
橋接模式(Bridge Pattern)
過濾器模式(Filter、Criteria Pattern)
組合模式(Composite Pattern)
裝飾器模式(Decorator Pattern)
外觀模式(Facade Pattern)
享元模式(Flyweight Pattern)
代理模式(Proxy Pattern)
行為型模式
特別關(guān)注對(duì)象之間的通信。主要解決的就是“類或?qū)ο笾g的交互”問題
責(zé)任鏈模式(Chain of Responsibility Pattern)
命令模式(Command Pattern)
解釋器模式(Interpreter Pattern)
迭代器模式(Iterator Pattern)
中介者模式(Mediator Pattern)
備忘錄模式(Memento Pattern)
觀察者模式(Observer Pattern)
狀態(tài)模式(State Pattern)
空對(duì)象模式(Null Object Pattern)
策略模式(Strategy Pattern)
模板模式(Template Pattern)
訪問者模式(Visitor Pattern)
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容