今年春節(jié)的時(shí)候绰疤,我在51CTO上選中了新的課程铜犬,就是高煥堂老師的EIT架構(gòu)。由于自己對架構(gòu)比較感興趣轻庆,剛好又有這種題材的講座癣猾,因此很愉快的學(xué)習(xí)了一下,感覺也有很多收獲榨了,在這里簡單的記錄一下
什么是EIT
實(shí)際上這個(gè)名詞我也是偶然間得知的煎谍,就是自己在聽高老師講的jni系列課程的時(shí)候,反復(fù)的提到了EIT龙屉,當(dāng)時(shí)一臉迷茫呐粘,什么是EIT呢?于是百度了一下转捕。發(fā)現(xiàn)似乎并沒有相關(guān)的介紹作岖。在高老師的個(gè)人主頁上找了半天也沒有找到。剛好發(fā)現(xiàn)高老師講的架構(gòu)相關(guān)的課程五芝,就是介紹EIT的講座痘儡,好奇的就學(xué)起來了。
老實(shí)說剛開始聽前幾集講座的時(shí)候枢步,還是不太明白高老師要表達(dá)的意思沉删。雖然老是提到EIT渐尿,EIT,但從始至終也沒有說明什么是E矾瑰,什么是I砖茸,什么是T。我也是看到了講座中的一張圖后殴穴,才大概明白了EIT的含義凉夯,也就是下面的圖:
也就是說E&&I是在一個(gè)類中的,T是其子類采幌,這是一個(gè)基本的形式
另一種擴(kuò)展的形式是這樣的
和上面的圖稍微有些不同劲够,接口層interface被抽象出來了
這個(gè)圖已經(jīng)給出了EIT的英文名稱,即engine休傍,interface和tire征绎,但是這三者是什么關(guān)系呢?engine是引擎的意思磨取,interface是接口炒瘸,而tire是輪胎。一開始我沒有明白高老師的含義寝衫,尤其是EIT的基本型而言,T是子類拐邪,怎么能說輪胎是引擎的子類呢慰毅?隨著學(xué)習(xí)的逐漸深入,我終于明白扎阶,原來這里的EIT是按照擴(kuò)展型中的圖定義的汹胃,也就是一個(gè)汽車引擎,需要有四個(gè)輪胎構(gòu)成东臀,這里引擎并不直接依賴于具體的輪胎着饥,而是依賴于接口interface,而tire實(shí)現(xiàn)了這個(gè)接口惰赋,于是在編碼中就很有彈性了宰掉,即,我需要什么輪胎我就組裝什么輪胎就可以了赁濒,甚至在運(yùn)行的時(shí)候轨奄,我還可以換輪胎。這樣的設(shè)計(jì)拒炎,顯然代碼會更有彈性挪拟。
設(shè)計(jì)模式
上面的兩種形式,大概是高老師在設(shè)計(jì)模式的基礎(chǔ)上击你,高度抽象而來玉组。實(shí)際上上面的兩個(gè)圖谎柄,正是代表了設(shè)計(jì)模式中的兩種經(jīng)典的模型。
首先我想介紹一下設(shè)計(jì)模式中的精髓惯雳,也就是接口朝巫。為什么說接口很重要呢?因?yàn)榻涌诳梢宰寖蓚€(gè)銜接的模塊解耦吨凑,讓雙方互相不知道對方的存在捍歪,這在編碼架構(gòu)中,具有非常好的彈性鸵钝。
設(shè)計(jì)模式有很多種糙臼,但最核心的就是五大原則了。在這五大原則中恩商,我覺得最重要的变逃,就是最后一個(gè)原則,就是開閉原則怠堪,即對擴(kuò)展是開放的揽乱,對修改是封閉的。也就是說粟矿,如何判斷我們寫的架構(gòu)好不好呢凰棉?其實(shí)很簡單,如果追加一個(gè)類似的功能陌粹,可不可以撒犀?這就是擴(kuò)展型。在擴(kuò)展的同時(shí)是否必須需要破壞之前的代碼掏秩?還是直接追加新的文件就可以了或舞,以前的文件可以最大程度的保持不變?這就是封閉性蒙幻。如果你寫的架構(gòu)映凳,同時(shí)具備擴(kuò)展型和封閉性,那么就真的是一個(gè)好的架構(gòu)了邮破。
舉個(gè)例子诈豌,如果需要實(shí)現(xiàn)一個(gè)架構(gòu),小明每天騎車上班决乎,我們應(yīng)該如何設(shè)計(jì)呢队询?
我想最直觀的設(shè)計(jì)就是下圖了
這實(shí)在是太簡單了,剛學(xué)會編程的人构诚,大概也可以寫的出來這個(gè)代碼蚌斩。
我們簡單的寫一下這個(gè)代碼評估一下
class Person {
void take(Bike* bike);
};
class Bike {
};
int main(int argc, char* argv) {
Person* person = new Person();
Bike* bike = new Bike();
person->take(bike);
return 0;
}
這真的不是很難。
該如何評估這個(gè)架子是否好呢?就用上面的兩個(gè)法則送膳,其一员魏,檢驗(yàn)是否有擴(kuò)展性,比如現(xiàn)在提出一個(gè)新需求叠聋,小明坐公交上班撕阎,這個(gè)架構(gòu)是否也同樣適用?當(dāng)然了可以適用碌补,只需要添加一個(gè)Bus類虏束,并且將Person類中take的參數(shù),改為Bus* bus就可以了厦章。其二镇匀,檢驗(yàn)是否是封閉的,我們?yōu)榱藬U(kuò)展新的需求袜啃,不得不修改已經(jīng)寫好的類中的代碼汗侵,即改變了Person中方法的參數(shù),這顯然不滿足封閉性群发。
特別的晰韵,如果我需要增加更多的交通工具,Person類就要不斷的修改熟妓,顯然雪猪,這個(gè)架構(gòu)設(shè)計(jì),smell bad起愈。
為什么這兩個(gè)特性都至關(guān)重要呢浪蹂?擴(kuò)展性就不必多說了,如果一個(gè)系統(tǒng)一點(diǎn)點(diǎn)擴(kuò)展性都沒有告材,顯然不會是一個(gè)很好的系統(tǒng)。為什么我們需要保持封閉性呢古劲?因?yàn)槲覀儾环忾]就意味著之前的代碼需要修改斥赋,那之前的一些工作無疑就白做了。比如Person類測試已經(jīng)充分驗(yàn)證产艾,完全沒有問題疤剑,可以給用戶使用,但是為了擴(kuò)展一個(gè)新的功能闷堡,我們不得不修改Person類隘膘,這樣之前的測試結(jié)果又要重來一遍,誰知道你引入的新的代碼杠览,會不會引起新的bug呢~
進(jìn)一步思考弯菊,為何我們的設(shè)計(jì)會破壞了開閉原則呢?就是因?yàn)槲覀冏尵唧w的東西Person踱阿,依賴于了具體的東西Bike了管钳。也就是兩個(gè)具體的東西耦合在了一起钦铁,因此Bike的改變必然會引起Person的改變。正確的做法才漆,應(yīng)該是依賴于抽象牛曹!因此我們要從交通工具中找到共通點(diǎn),從而抽象出抽象的接口
好了醇滥,這樣一來黎比,Person這個(gè)具體的東西,依賴于Traffic這個(gè)接口鸳玩,那么Bike的改變就不會引起Person的改變了
簡單的代碼如下
class Traffic;
class Person {
void take(Traffic* trafic);
};
class Traffic {
};
class Bike : public Traffic {
};
class Bus : public Traffic {
};
int main(int argc, char* argv[]) {
Person* person = new Person();
Traffic* traffic = new Bike();
person->take(traffic);
}
這樣的設(shè)計(jì)阅虫,顯然比剛才的設(shè)計(jì)就改良了不少,如果你還想擴(kuò)展新的交通工具怀喉,只要做相應(yīng)的子類就可以了书妻,這個(gè)擴(kuò)展,也不會引起Person類的修改躬拢,完全符合設(shè)計(jì)模式的原則躲履,真的很不錯(cuò)。
說到這里聊闯,我們其實(shí)就描述了一個(gè)設(shè)計(jì)模式:策略模式工猜。什么是策略模式呢?我們?nèi)ド习鄬?shí)際上是一種策略菱蔬,也就是這里的traffic接口篷帅,而具體的策略,比如是騎自行車拴泌,還是做公交車之類的由子類決定魏身。
EIT與設(shè)計(jì)模式
介紹完設(shè)計(jì)模式,特別是策略模式蚪腐,我們可以在EIT中找到策略模式的影子箭昵。E可以看做是Person,I可以看做是Traffic接口回季,而Bike以及Bus家制,就是具體的T。所以擴(kuò)展的EIT模式泡一,就可以看做是設(shè)計(jì)模式中的策略模式颤殴。
對于標(biāo)準(zhǔn)的EIT模型,設(shè)計(jì)模式中也有與其相對應(yīng)的一種鼻忠,即模板模式涵但。模板模式的應(yīng)用場景是這樣的,做一件事,可能分為幾個(gè)步驟贤笆,但是在做具體的步驟的時(shí)候蝇棉,不同的用戶做法不同,我們將這些具體的步驟做成接口芥永,讓用戶在實(shí)現(xiàn)子類中實(shí)現(xiàn)篡殷。比如,我們在排序的時(shí)候需要知道如何判斷兩個(gè)數(shù)的大小埋涧,這個(gè)具體的算法板辽,由用戶決定,因此做成接口棘催。用戶在準(zhǔn)備排序時(shí)劲弦,只需要提供如何判斷兩個(gè)數(shù)大小的方法就可以,至于如何排好序醇坝,基類就已經(jīng)實(shí)現(xiàn)好了邑跪,用戶不需要知道。
因此從這里看來呼猪,EIT的基本形式和擴(kuò)展形式画畅,實(shí)際上就對應(yīng)了設(shè)計(jì)模式中的兩種經(jīng)典的模式模板模式以及策略模式。讓我們繼續(xù)挖掘一下這兩個(gè)形式宋距≈狨猓基本形式是E&&I做在了一起,即在一個(gè)類中谚赎,而T繼承了I淫僻,這就是一個(gè)典型的繼承關(guān)系,也就是is-a的關(guān)系壶唤。而擴(kuò)展形式是E有一個(gè)接口I雳灵,T實(shí)現(xiàn)了接口I,這就是一個(gè)has-a的關(guān)系闸盔。這兩種關(guān)系各有利弊细办。設(shè)計(jì)模式中的建議,是讓我們盡量多用組合的方式蕾殴,即has-a的關(guān)系,而少用繼承的關(guān)系岛啸,即用組合來替代繼承钓觉。這樣做的好處時(shí),組合關(guān)系在運(yùn)行時(shí)是可以替換的坚踩。比如小明騎車上班荡灾,在中間突然想換公交車了,這也是完全可以的。
內(nèi)容與形式
我們說EIT有兩種形式批幌,基本形式和擴(kuò)展形式础锐,什么是形式呢?形式就是一種套路荧缘。高老師的意思是我們需要編碼的任何內(nèi)容都可以放在這個(gè)形式中皆警。你看,這里就提到了內(nèi)容和形式截粗。我們需要編碼的信姓,就是內(nèi)容,EIT就是形式绸罗,也就是意推,我們需要編碼的任何東西,都可以以EIT的形式呈現(xiàn)珊蟀。
比如剛才提到的小明騎車上班菊值,就是內(nèi)容,我們剛才用策略模式實(shí)現(xiàn)育灸,即EIT的擴(kuò)展型實(shí)現(xiàn)腻窒,就是一種形式。也就是我們用EIT的形式描扯,實(shí)現(xiàn)了小明騎車上班的內(nèi)容定页。
那么,真的所有復(fù)雜的代碼架構(gòu)绽诚,都可以通過EIT的形式展示嗎典徊?實(shí)際上是可以的,但絕不僅僅是一種形式恩够,復(fù)雜代碼架構(gòu)卒落,是由EIT這種簡單的形式組合而成。
事實(shí)上蜂桶,這里蘊(yùn)含的思想非常重要:任何復(fù)雜的東西都是由簡單的東西堆砌而成儡毕。所以在拿到一個(gè)復(fù)雜的架構(gòu)的時(shí)候,我們需要將他首先拆解成幾個(gè)簡單東西的組合扑媚,有的時(shí)候簡單的東西還需要繼續(xù)拆解腰湾。而拆解到最后的功能或者功能組,也就是內(nèi)容疆股,都可以通過EIT這種形式展示出來费坊,最后,我們將這些EIT的形式組合到一起旬痹,ok附井,復(fù)雜的架構(gòu)就開發(fā)出來了讨越。
我覺得這種思想也體現(xiàn)了軟件架構(gòu)設(shè)計(jì)的重要思路。
因此拿到一個(gè)需求永毅,我們首先需要將需求分解把跨,再提取出接口,分的好沼死,接口設(shè)計(jì)的好着逐,架構(gòu)就越好。
強(qiáng)龍不壓地頭蛇
高老師進(jìn)一步提出了強(qiáng)龍不壓地頭蛇的思想漫雕。什么是強(qiáng)龍滨嘱?強(qiáng)龍做的就是E&&I,什么是地頭蛇浸间?地頭蛇做的就是T
說的直觀一點(diǎn)太雨,強(qiáng)龍就是做框架設(shè)計(jì)的,而地頭蛇魁蒜,就是做定制化開發(fā)的囊扳。
比如Android,谷歌的程序員們兜看,就是強(qiáng)龍锥咸,他們負(fù)責(zé)做框架的開發(fā),他們將功能與任務(wù)進(jìn)行分解细移,并且流出了必要的接口I搏予,比如Activity中流出了接口OnCreate,OnDestroy等等弧轧。而我們這些程序開發(fā)者雪侥,就是要實(shí)現(xiàn)強(qiáng)龍們?yōu)槲覀冊O(shè)計(jì)好的接口,做定制化的開發(fā)精绎。這樣速缨,就產(chǎn)生了形形色色的不同的Android應(yīng)用。
因此架構(gòu)師們的核心工作就是代乃,將系統(tǒng)進(jìn)行拆解旬牲,設(shè)計(jì)出接口I,接口的設(shè)計(jì)搁吓,體現(xiàn)了一個(gè)架構(gòu)師的能力原茅,接口設(shè)計(jì)的好,地頭蛇們開發(fā)的就會非常開心堕仔。
尾聲
好了擂橘,對于高老師的這套架構(gòu)講座,就暫時(shí)總結(jié)到這里了贮预。
我覺得自己的收獲還是很大的贝室。高老師提出的EIT的思路確實(shí)很有借鑒意義。