真實(shí)工作中,你是否是這樣操作過:一個(gè)需求過來,把原來的代碼改一遍,再一個(gè)需求過來,又把上一個(gè)需求的代碼改一遍,很多重復(fù)的工作還是在日復(fù)一日的重復(fù),有什么好的辦法改善嗎?
相信有經(jīng)驗(yàn)的小伙伴一定聽過:對擴(kuò)展開放,對修改關(guān)閉。那么,你知道這句話的真正含義嗎?今天我們來聊聊開閉原則到底是怎么實(shí)現(xiàn)的。
開放封閉原則,英文是:Open–closed principle, 簡稱OCP,是該原則是 Bertrand Meyer 在1988年提出的,最后被 Robert C. Martin收錄到 SOLID原則,開閉原則指出:
Software entities should be open for extension, but closed for modification.
軟件實(shí)體應(yīng)該對擴(kuò)展開放,對修改關(guān)閉。
"對擴(kuò)展開放,對修改關(guān)閉",如何理解呢?我們先看一個(gè)案例,如下圖,給出了電商領(lǐng)域庫存系統(tǒng)庫存變更的簡易模型圖,庫存系統(tǒng)接收外部系統(tǒng)庫存變更事件,然后對數(shù)據(jù)庫中的庫存進(jìn)行修改。

面對這個(gè)業(yè)務(wù)需求,很多人的代碼會寫出這樣:
public class Stock { public void updateStock(String event){ if("outOfStock" == event){ // todo 出庫事件 庫存操作 }else if("warehousing" == event){ // todo 入庫事件 庫存操作 } }}這時(shí),新的需求來了:WMS倉儲系統(tǒng)內(nèi)部會產(chǎn)生盤點(diǎn)事件(盤盈/盤虧),這些事件會導(dǎo)致變更庫存。于是,代碼就會發(fā)展成下面這樣:
public class Stock { public void updateStock(String event){ if("outOfStock" == event){ // todo 出庫事件 庫存操作 }else if("warehousing" == event){ // todo 入庫事件 庫存操作 }else if("panSurplus" == event){ // todo 盤盈事件 庫存操作 }else if("loss" == event){ // todo 盤虧事件 庫存操作 } }}很顯然,上述代碼的實(shí)現(xiàn),每來一個(gè)需求,就需要修改一次代碼,在方法中增加一個(gè) else if分支,因此 Stock類就一直處于變更中,不穩(wěn)定。
有沒有什么好的辦法,可以使得這個(gè)代碼不用被修改,但是又能夠靈活的擴(kuò)展,滿足業(yè)務(wù)需求呢?
這個(gè)時(shí)候我們就要搬出 java的三大法寶:繼承,實(shí)現(xiàn),多態(tài)。
我們發(fā)現(xiàn)整個(gè)業(yè)務(wù)模型是:事件導(dǎo)致庫存變更。所以,能不能把事件抽離出來?把它抽象成一個(gè)接口,代碼如下:
public interface Event { void updateStock(String event);}每種事件對應(yīng)一種庫存變更,抽象成一個(gè)具體的實(shí)現(xiàn)類,代碼如下:
入庫事件
public class WarehousingEvent implements Event { public void updateStock(String event){ // 業(yè)務(wù)邏輯 }}出庫事件
public class OutOfStockEvent implements Event { public void updateStock(String event){ // 業(yè)務(wù)邏輯 }}xxx事件
public class XXXEvent implements Event { public void updateStock(String event){ // 業(yè)務(wù)邏輯 }}最后,Stock類中 updateStock()庫存變更邏輯就可以抽象成下面這樣:
public class Stock { public void updateStock(String event){ // 根據(jù)事件類型獲取真實(shí)的實(shí)現(xiàn)類 Event event = getEventInstance(event); // 庫存變更操作 event.updateStock(); }}經(jīng)過抽象、分離和改造之后,Stock.updateStock()類就穩(wěn)定下來了,再也不需要每增加一個(gè)事件就需要增加一個(gè) else if分支處理,這種抽象帶來的好處也是很明顯的:每次有新的庫存變更事件,只需要增加一個(gè)實(shí)現(xiàn)類,其他的邏輯都不需要更改,當(dāng)庫存事件無效時(shí)只需要把實(shí)現(xiàn)類刪除即可。
在Java編程中,遵循開閉原則的常見方式有:使用抽象類和接口、使用策略模式、使用裝飾器模式等。
抽象類和接口是 Java中實(shí)現(xiàn) 開閉原則的基礎(chǔ),通過定義抽象類或接口,程序員可以在不修改已有代碼的情況下,通過繼承或?qū)崿F(xiàn)來擴(kuò)展新功能。因此,我們強(qiáng)烈建議:面向接口編程。
策略模式是一種行為設(shè)計(jì)模式,允許在運(yùn)行時(shí)選擇算法的實(shí)現(xiàn),策略模式通過定義一系列算法,并將每個(gè)算法封裝在獨(dú)立的類中,使得它們可以相互替換。
在上面的示例講解中,其實(shí)使用的就是策略模式,當(dāng)后期有其他的庫存事件時(shí),我們只需要添加擴(kuò)展類,而無需修改現(xiàn)有的代碼。
裝飾器模式是一種結(jié)構(gòu)設(shè)計(jì)模式,允許向一個(gè)對象動態(tài)添加行為。裝飾器模式通過創(chuàng)建一個(gè)裝飾器類來包裝原始類,從而增加新的功能。示例代碼:
// 定義一個(gè)接口public interface Coffee { String getDescription(); double getCost();}// 實(shí)現(xiàn)接口的具體類public class SimpleCoffee implements Coffee { @Override public String getDescription() { return "Simple Coffee"; } @Override public double getCost() { return 5.0; }}// 創(chuàng)建裝飾器抽象類public abstract class CoffeeDecorator implements Coffee { protected Coffee decoratedCoffee; public CoffeeDecorator(Coffee coffee) { this.decoratedCoffee = coffee; } @Override public String getDescription() { return decoratedCoffee.getDescription(); } @Override public double getCost() { return decoratedCoffee.getCost(); }}// 實(shí)現(xiàn)具體的裝飾器類public class MilkDecorator extends CoffeeDecorator { public MilkDecorator(Coffee coffee) { super(coffee); } @Override public String getDescription() { return decoratedCoffee.getDescription() + ", Milk"; } @Override public double getCost() { return decoratedCoffee.getCost() + 1.5; }}public class SugarDecorator extends CoffeeDecorator { public SugarDecorator(Coffee coffee) { super(coffee); } @Override public String getDescription() { return decoratedCoffee.getDescription() + ", Sugar"; } @Override public double getCost() { return decoratedCoffee.getCost() + 0.5; }}// 客戶端代碼public class CoffeeShop { public static void main(String[] args) { Coffee coffee = new SimpleCoffee(); System.out.println(coffee.getDescription() + " $" + coffee.getCost()); coffee = new MilkDecorator(coffee); System.out.println(coffee.getDescription() + " $" + coffee.getCost()); coffee = new SugarDecorator(coffee); System.out.println(coffee.getDescription() + " $" + coffee.getCost()); }}在這個(gè)示例中,Coffee接口定義了獲取描述和成本的方法,SimpleCoffee類實(shí)現(xiàn)了這個(gè)接口。CoffeeDecorator類是一個(gè)抽象類,實(shí)現(xiàn)了 Coffee接口,并持有一個(gè) Coffee對象。MilkDecorator和SugarDecorator類分別繼承了CoffeeDecorator類,并擴(kuò)展了其功能。如果我們需要增加新的裝飾器,只需要繼承 CoffeeDecorator類并實(shí)現(xiàn)其方法即可,而無需修改現(xiàn)有的代碼。
本文通過一個(gè)電商中庫存實(shí)例,演示了開閉原則的整個(gè)抽象和實(shí)現(xiàn)過程,并給出了開閉原則最常用的 3種實(shí)現(xiàn)方式。
開閉原則的核心是對擴(kuò)展開放,對修改關(guān)閉,因此,當(dāng)業(yè)務(wù)需求一直需要修改同一段代碼時(shí),我們就得多思考代碼修改的理由是什么?它們之間是不是有一定的共同性?能不能把這些變更點(diǎn)分離出來,通過擴(kuò)展來實(shí)現(xiàn)而不是修改代碼?
其實(shí)在業(yè)務(wù)開發(fā)中還有很多類似的場景,比如:電商系統(tǒng)中的會員系統(tǒng),需要根據(jù)用戶不同的等級計(jì)算不同的費(fèi)用;機(jī)票系統(tǒng),根據(jù)用戶不同的等級(普通,白金用戶,黃金用戶...)提供不同的售票機(jī)制;網(wǎng)關(guān)系統(tǒng)中,根據(jù)不同的粒度(接口,ip,服務(wù),集群)來實(shí)現(xiàn)限流;
可能有小伙伴會反駁,業(yè)務(wù)場景有類似的場景,但是邏輯簡單,幾個(gè) if-else就搞定了,沒有必要去搞這么復(fù)雜的設(shè)計(jì)。
本人建議:功夫在平時(shí),功夫在細(xì)節(jié)。
很多人總抱怨業(yè)務(wù)開發(fā)技術(shù)成長慢,特別是對于初級程序員,但是大部門人的起點(diǎn)都是業(yè)務(wù)的 CRUD,如果能在業(yè)務(wù) CRUD過程中想辦法"挖掘"某些 設(shè)計(jì)模式,通過這種長期的刻意練習(xí),量變產(chǎn)生質(zhì)變,慢慢就能領(lǐng)會這些經(jīng)典設(shè)計(jì)原則的奧妙,終有一天也能寫出讓人賞心悅目的代碼。
本文鏈接:http://m.www897cc.com/showinfo-26-100459-0.html開閉原則,開放的是什么?關(guān)閉的又是什么?
聲明:本網(wǎng)頁內(nèi)容旨在傳播知識,若有侵權(quán)等問題請及時(shí)與本網(wǎng)聯(lián)系,我們將在第一時(shí)間刪除處理。郵件:2376512515@qq.com
上一篇: IDC:蘋果 Vision Pro 今年銷量不超過 50 萬臺,平價(jià)款明年發(fā)布
下一篇: Python 數(shù)據(jù)類型(如整數(shù)、浮點(diǎn)數(shù)、字符串、列表、元組、字典)