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)
設(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.問題思考的分析
- 什么叫作開閉原則,他的主要用途是什么?
- 如何做到拓展開放,修改封閉這一準(zhǔn)則,結(jié)合案例說(shuō)一下如何實(shí)現(xiàn)?
- 你平常是如何理解開閉原則的,判斷的標(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ù)有以下幾種:
- 抽象類和接口:通過定義抽象類和接口來(lái)約定行為,然后通過繼承和實(shí)現(xiàn)這些抽象類和接口來(lái)擴(kuò)展功能。
- 策略模式:將算法的實(shí)現(xiàn)分離到不同的類中,通過組合方式來(lái)實(shí)現(xiàn)不同的行為。
- 裝飾器模式:通過對(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è)代碼,初看是符合要求了,再想想,要是我增加一種形狀呢? 比如增加三角形.
- 首先,要增加一個(gè)三角形的類, 繼承自Shape
- 第二,要增加一個(gè)畫三角形的方法drawTriage()
- 第三,在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)增加一種類型三角形. 只需要
- 第一,增加一個(gè)三角形的類,實(shí)現(xiàn)Shape接口
- 第二,調(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ì)怎么思考呢?
- 首先有一個(gè)銀行業(yè)務(wù)類, 用來(lái)處理銀行的業(yè)務(wù)
- 銀行有哪些業(yè)務(wù)呢? 存錢,取錢,轉(zhuǎn)賬, 這都是銀行要執(zhí)行的操作
- 那外部說(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)
- 提高了代碼的可維護(hù)性與復(fù)用性:遵循開閉原則可以讓代碼更加穩(wěn)定和可維護(hù),同時(shí)也使得代碼更容易被復(fù)用。如果我們需要修改某個(gè)模塊的行為,只需要擴(kuò)展該模塊而不需要直接修改源代碼,這樣就不會(huì)影響到其他的模塊。
- 提高了代碼的可擴(kuò)展性:開閉原則還可以提高代碼的可擴(kuò)展性。通過擴(kuò)展已有的代碼,我們可以很容易地添加新的功能或改進(jìn)現(xiàn)有功能,從而適應(yīng)業(yè)務(wù)需求的更改。
- 提高了代碼的可測(cè)試性:遵循開閉原則可以降低代碼的耦合度,使得測(cè)試更加容易。因?yàn)槲覀冎恍枰獪y(cè)試新增的代碼,而不必驗(yàn)證已有代碼的正確性。
8.2 開閉原則的缺點(diǎn)
- 對(duì)代碼的設(shè)計(jì)要求高:遵循開閉原則需要對(duì)代碼進(jìn)行良好的抽象和封裝,這對(duì)程序員的設(shè)計(jì)能力和經(jīng)驗(yàn)要求較高。如果設(shè)計(jì)不好,可能會(huì)導(dǎo)致代碼過于復(fù)雜難以維護(hù)。
- 可能會(huì)增加代碼量:通過擴(kuò)展已有的代碼來(lái)實(shí)現(xiàn)新功能,可能會(huì)增加代碼量,使得系統(tǒng)變得更加復(fù)雜。這需要我們?cè)谄胶饪删S護(hù)性和代碼量之間做出權(quán)衡。
- 可能會(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)要注意。
- 第一點(diǎn)是,開閉原則并不是說(shuō)完全杜絕修改,而是以最小的修改代碼的代價(jià)來(lái)完成新功能的開發(fā)。
- 第二點(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é)
- 問題思考:開閉原則的主要用途是什么?如何才能做到對(duì)外拓展開放,對(duì)內(nèi)修改關(guān)閉?你平常是如何理解開閉原則的,判斷的標(biāo)準(zhǔn)是什么?
- 如何理解開閉原則:軟件實(shí)體(模塊、類、方法等)應(yīng)該“對(duì)擴(kuò)展開放、對(duì)修改關(guān)閉”。
- 為何開閉原則比較難學(xué):怎樣的代碼改動(dòng)才被定義為‘修改’?怎么才算滿足或違反‘開閉原則’?如何理解大部分設(shè)計(jì)模式都是遵循開閉原則!
- 開筆原則的背景:軟件/業(yè)務(wù)迭代升級(jí)中,面對(duì)代碼變化,修改原來(lái)代碼可能引入原有模塊bug風(fēng)險(xiǎn),因此盡量通過對(duì)外拓展來(lái)實(shí)現(xiàn)功能迭代。
- 實(shí)現(xiàn)開閉原則的方式:常用的設(shè)計(jì)技術(shù)有以下幾種,1.抽象類和接口;2.策略模式;3.裝飾器模式等。
- 開閉原則的案例教學(xué):繪制圓形/矩形,通過if-else來(lái)執(zhí)行不同case邏輯,新增一種類型則需要修改原邏輯。因此考慮通過接口+抽象類方式實(shí)現(xiàn)友好拓展。
- 開閉原則的優(yōu)點(diǎn):1.提高代碼拓展性和可維護(hù)性;2.提高代碼可測(cè)試的粒度;3.降低的代碼耦合度。
- 開閉原則的缺點(diǎn):1.對(duì)代碼設(shè)計(jì)要求高;2.可能會(huì)導(dǎo)致代碼量增大和變得復(fù)雜;3.可能會(huì)帶來(lái)設(shè)計(jì)上的限制。
- 總結(jié)如何理解開閉原則:當(dāng)需求發(fā)生變化時(shí),我們應(yīng)該通過添加新的代碼來(lái)擴(kuò)展功能,而不是修改已有的代碼。
- 總結(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) |