軟件設(shè)計原則之第一篇——開閉原則(OCP)

這篇是軟件設(shè)計原則系列文章的第一篇钩蚊,之前寫過一篇博客里面介紹了七種設(shè)計原則,但是將七種原則容納到一篇文章之中總感覺哪里不對:說的太多文章就會變得冗長影響閱讀體驗言疗,說的太少總感覺有的話沒說完就進(jìn)行下一項了冰木,于是乎本人突發(fā)奇想為何不分開來寫妻熊?更于是乎就有了“軟件設(shè)計原則系列文章”饼丘,本文是個開篇趁桃。廢話不多說了,Let's come to the point!

軟件設(shè)計一共有七大原則镇辉,在這七大原則之上還有一個總體原則屡穗,也就是我們常說的“高內(nèi)聚贴捡,低耦合”忽肛。它們之間就好比白雪公主和七個小矮人的關(guān)系,那它們叫什么名字呢烂斋?讓我們先來認(rèn)識一下屹逛,它們分別是:開閉原則(OCP)、依賴倒置原則(DIP)汛骂、單一職責(zé)原則(SRP)罕模、接口隔離原則(ISP)、迪米特原則(LoD)帘瞭、里氏替換原則(LSP)和合成復(fù)用原則(CARP)淑掌。今天的主角是這七個矮人中的姚明,它就是開閉原則(OCP蝶念,Open Close Principle)抛腕。

門一開一閉,眼一閉一睜媒殉,這也算個原則担敌?


沒錯,但這里的開閉不是指門廷蓉,而是對擴(kuò)展開放對修改關(guān)閉全封,用擴(kuò)展而不是修改來適應(yīng)需求的變化。有句話說的好——唯一不變的是變化本身桃犬,應(yīng)用到軟件界就是需求是一定會變化的刹悴,即使原來的不變新的東西也會加進(jìn)來,如果你想一勞永逸攒暇,開發(fā)的東西永遠(yuǎn)不會變土匀,那只有一種可能——這個系統(tǒng)已經(jīng)沒人在用了。怎么證明一個軟件或者系統(tǒng)還活著扯饶?我們來看一下身邊的例子:Win10是不是會不定時地更新升級恒削?Android和IOS等常用的手機(jī)系統(tǒng)是不是會經(jīng)常提示你升級?我們經(jīng)常玩兒的游戲是不是會經(jīng)常打補(bǔ)丁……那什么樣的代碼不需要維護(hù)了呢尾序?在我上大學(xué)的時候最火的手機(jī)品牌之一——諾基亞钓丰,當(dāng)時它安裝的系統(tǒng)塞班現(xiàn)在已經(jīng)被淘汰,這個代碼已經(jīng)不需要維護(hù)了(壞了每币,暴露年齡了)⌒。現(xiàn)在是2020年的3月份,就在兩個月前的1月14日,微軟官方宣布Win 7操作系統(tǒng)退役梦鉴,從此陪伴了大家10年之久的Windows 7正式退出歷史舞臺李茫,它的代碼也不需要維護(hù)了,更早的像什么Windows XP啊肥橙、Windows 98啊就更不用說了(再一次暴露年齡??)魄宏,估計再過十幾年的學(xué)生都不一定聽說過這些操作系統(tǒng)。說了這么多存筏,其實想表達(dá)的就一點:既然變化是一定要發(fā)生的宠互,那我們應(yīng)該應(yīng)用什么策略以及采取什么措施來應(yīng)對變化,我覺得這是所有軟件設(shè)計者包括開發(fā)者應(yīng)該考慮的問題椭坚。哈哈予跌,值得慶幸的是早已有前輩為我們考慮好了這些事情,他們總結(jié)的經(jīng)驗經(jīng)過了大量工程實踐的考驗并被公認(rèn)為行之有效的方法善茎,從而逐漸形成原則沉淀下來為后人所用券册,這就是軟件設(shè)計的原則。

我們在討論本篇文章的主角——OCP之前再來廢一些話垂涯,那就是如果不遵守這七大原則能不能行烁焙?就問行!不集币!行考阱!答案是可以的,沒問題鞠苟,代碼照樣能運行乞榨!那你還扯個哩個兒愣~~散了,散了当娱,都散了吃既,各回各家吧,博主現(xiàn)場翻車跨细!~~嘖嘖嘖……我是不是在這扯皮一會兒就知道了鹦倚,下面用實際的代碼來感受一下用OCP和不用OCP的差別。

我們假設(shè)一個場景:小明買車冀惭,今天小明買了一輛奔馳震叙,開著奔馳去兜風(fēng)。

public class Benz {

public void run() {

System.out.println("Benz run...");

}

}

public class Person {

private String name;

public Person(String name) {

super();

this.name = name;

}

public String getName() {

return name;

}

public void driveACar(Benz benz) {

System.out.println(getName());

benz.run();

}

}

public class OCP {

public static void main(String[] args) {

Person xiaoMing = new Person("小明");

Benz benz = new Benz();

xiaoMing.driveACar(benz);

}

}

main方法模擬的是客戶端程序散休,Person模擬的是服務(wù)端程序媒楼,這樣看起來沒什么問題,確實也沒什么問題戚丸。然后需求變了:小明換車了划址,把奔馳換成了寶馬,然后開著寶馬去兜風(fēng)。你會說那還不簡單夺颤,改一下不就行了嗎痢缎?

public class BMW {// 增加一個寶馬類

public void run() {

System.out.println("BMW run...");

}

}

public class Person {

private String name;

public Person(String name) {

super();

this.name = name;

}

public String getName() {

return name;

}

public void driveACar(Benz benz) {

System.out.println(getName());

benz.run();

}

public void driveACar(BMW bmw) {// 增加開寶馬的方法

System.out.println(getName());

bmw.run();

}

}

public class OCP {

public static void main(String[] args) {

Person xiaoMing = new Person("小明");

// Benz benz = new Benz();

// xiaoMing.driveACar(benz);

BMW bmw = new BMW();// 修改客戶端代碼:注釋掉原有調(diào)用開奔馳的方法,新增調(diào)用開寶馬的方法

xiaoMing.driveACar(bmw);

}

}

我們來看一下這一次的需求改動需要做什么工作:首先服務(wù)端代碼Person要增加一個開寶馬的方法世澜,因為原有開奔馳的方法不能傳入寶馬對象所以只能新增一個独旷;其次,客戶端也要改變調(diào)用的代碼宜狐,因為原先調(diào)用開奔馳的方法已不滿足新的需求势告。所以,Look一下抚恒,一個小小的需求改動會牽扯到從服務(wù)端到客戶端整體的改造,所謂牽一發(fā)而動全身络拌。也許你又會問:這樣不行嗎俭驮?在本例中我只是用很簡單的代碼來舉栗子,實際項目里面代碼可能會很復(fù)雜春贸,所以需求變化可能會改很多代碼混萝,工作量比較大;另一方面萍恕,改造原有代碼意味著引入風(fēng)險逸嘀,因為原有代碼可能是經(jīng)過了很長時間的“磨合”,功能已經(jīng)非常穩(wěn)定了允粤,你去改造它能保證百分百不引入bug嗎崭倘?很有可能你改造A功能的代碼,但是卻無意間影響了原有的B功能类垫,導(dǎo)致原有B功能不可用或出現(xiàn)問題司光。所以,面對新的需求我們不嚴(yán)格要求絕對不能改動原有代碼悉患,但我們應(yīng)該做到的是盡可能少的改動残家。拿本例來說,如果小明再換一輛奧迪呢售躁?是不是Person又得加一個開奧迪的方法坞淮?如果我們將這些具體品牌的車抽象出一個更高層的概念——汽車類,那么你需要開什么車就傳入子類(或?qū)崿F(xiàn)類)不就可以了嗎陪捷?下面我們就按照這一思路來對上述代碼做改造:

public interface ICar {

/**

* 將具體的車抽象出來形成一個更高層的概念回窘,可以是一個接口或者抽象類,

* 它里面有一個run方法揩局,所有具體品牌的車都繼承或者實現(xiàn)這個更高層的類毫玖。

*/

void run();

}

public class Benz implements ICar {

public void run() {

System.out.println("Benz run...");

}

}

public class BMW implements ICar{

public void run() {

System.out.println("BMW run...");

}

}

public class Person {

private String name;

public Person(String name) {

super();

this.name = name;

}

public String getName() {

return name;

}

public void driveACar(ICar car) {

System.out.println(getName());

car.run();

}

}

public class OCP {

private static ICar car;

static {

// 模擬Spring注入ICar實例,需要不同品牌的車就注入不同品牌的實例即可

car = new Benz();

}

public static void main(String[] args) {

Person person = new Person("小明");

person.driveACar(car);

}

}

這樣服務(wù)提供端Person就提供一個driveACar方法即可,入?yún)⑹且粋€接口付枫,所有具體品牌的車都實現(xiàn)ICar接口烹玉,這樣需要什么車就傳入相關(guān)的實現(xiàn)類即可,在客戶端需要什么車就注入一個汽車實例即可阐滩,所以面對小明不斷地?fù)Q車二打,只需要擴(kuò)展ICar的實現(xiàn)類即可,客戶端(main方法)和服務(wù)提供方(Person)都不需要改代碼掂榔,這就是“對擴(kuò)展開放继效,對修改關(guān)閉”,其實面向抽象(接口)編程就是這個道理装获。如果再放開一點瑞信,可以把Person也抽象成接口,這樣不僅小明可以開奔馳穴豫,張三凡简、李四都可以開,需要不同的人開車就在客戶端注入不同個人的實例精肃,這一點就不在這里用代碼演示了秤涩。

所以,到這里讀者應(yīng)該明白什么是OCP了以及遵循OCP原則會帶來哪些好處司抱,本文也接近尾聲了筐眷。最后說一下為什么在文章開頭處我用“白雪公主和七個小矮人”來做比喻,目的是為了讓讀者記住“7”這個數(shù)字习柠,可能你看完這個系列文章之后過一段時間會忘記一共有幾大原則匀谣,今后哪天如果被面試官問到只要你腦海記住有七個小矮人就可以了。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末津畸,一起剝皮案震驚了整個濱河市振定,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌肉拓,老刑警劉巖后频,帶你破解...
    沈念sama閱讀 221,695評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異暖途,居然都是意外死亡卑惜,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,569評論 3 399
  • 文/潘曉璐 我一進(jìn)店門驻售,熙熙樓的掌柜王于貴愁眉苦臉地迎上來露久,“玉大人,你說我怎么就攤上這事欺栗『梁郏” “怎么了征峦?”我有些...
    開封第一講書人閱讀 168,130評論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長消请。 經(jīng)常有香客問我栏笆,道長,這世上最難降的妖魔是什么臊泰? 我笑而不...
    開封第一講書人閱讀 59,648評論 1 297
  • 正文 為了忘掉前任蛉加,我火速辦了婚禮,結(jié)果婚禮上缸逃,老公的妹妹穿的比我還像新娘针饥。我一直安慰自己,他們只是感情好需频,可當(dāng)我...
    茶點故事閱讀 68,655評論 6 397
  • 文/花漫 我一把揭開白布丁眼。 她就那樣靜靜地躺著,像睡著了一般贺辰。 火紅的嫁衣襯著肌膚如雪户盯。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,268評論 1 309
  • 那天饲化,我揣著相機(jī)與錄音,去河邊找鬼吗伤。 笑死吃靠,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的足淆。 我是一名探鬼主播巢块,決...
    沈念sama閱讀 40,835評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼巧号!你這毒婦竟也來了族奢?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,740評論 0 276
  • 序言:老撾萬榮一對情侶失蹤丹鸿,失蹤者是張志新(化名)和其女友劉穎越走,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體靠欢,經(jīng)...
    沈念sama閱讀 46,286評論 1 318
  • 正文 獨居荒郊野嶺守林人離奇死亡廊敌,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,375評論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了门怪。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片骡澈。...
    茶點故事閱讀 40,505評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖掷空,靈堂內(nèi)的尸體忽然破棺而出肋殴,到底是詐尸還是另有隱情囤锉,我是刑警寧澤,帶...
    沈念sama閱讀 36,185評論 5 350
  • 正文 年R本政府宣布护锤,位于F島的核電站官地,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏蔽豺。R本人自食惡果不足惜区丑,卻給世界環(huán)境...
    茶點故事閱讀 41,873評論 3 333
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望修陡。 院中可真熱鬧沧侥,春花似錦、人聲如沸魄鸦。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,357評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽拾因。三九已至旺罢,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間绢记,已是汗流浹背扁达。 一陣腳步聲響...
    開封第一講書人閱讀 33,466評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留蠢熄,地道東北人跪解。 一個月前我還...
    沈念sama閱讀 48,921評論 3 376
  • 正文 我出身青樓,卻偏偏與公主長得像签孔,于是被迫代替她去往敵國和親叉讥。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,515評論 2 359

推薦閱讀更多精彩內(nèi)容

  • DAY 05 1饥追、 public classArrayDemo { public static void mai...
    周書達(dá)閱讀 689評論 0 0
  • 主要是對一個Java開發(fā)的注意點和易忘點做個小總結(jié)图仓,不少地方?jīng)]有詳細(xì)分析,讀者見諒但绕。另外救崔,對一些特殊的示例程序做一...
    androidjp閱讀 1,288評論 0 15
  • 一、單一職責(zé) 定義:應(yīng)該有且僅有一個原因引起類的變更壁熄。 優(yōu)點:1帚豪、類的復(fù)雜性降低,實現(xiàn)什么職責(zé)都有清晰明確的定義2...
    李月半Android閱讀 248評論 0 0
  • 本文包括:1草丧、Listener簡介2狸臣、Servlet監(jiān)聽器3、監(jiān)聽三個域?qū)ο髣?chuàng)建和銷毀的事件監(jiān)聽器4昌执、監(jiān)聽三個域?qū)?..
    廖少少閱讀 6,086評論 6 28
  • (一) 開春的時候烛亦,帕靈德森林搖落了一身白皚皚的雪裝诈泼,在沖破昏暗積云的陽光下,粗壯的白樺樹越發(fā)顯得挺拔壯實煤禽。雖然莫...
    不劉閱讀 1,414評論 0 1