1. 從依賴倒置說(shuō)起
首先,我們來(lái)看下《敏捷軟件開發(fā)》中對(duì)依賴倒置的說(shuō)明:
a. 高層模塊不應(yīng)該依賴于低層模塊,二者都應(yīng)該依賴于抽象。
b. 抽象不應(yīng)該依賴于細(xì)節(jié),細(xì)節(jié)應(yīng)該依賴于抽象。
我們先拋開第二點(diǎn)來(lái)看第一點(diǎn),什么叫高層模塊,什么叫低層模塊。在我理解來(lái)看:高層模塊也就是戰(zhàn)略性模塊,業(yè)務(wù)性模塊。而低層模塊就是戰(zhàn)術(shù)性模塊,細(xì)節(jié)類模塊。
先來(lái)看這樣一段代碼:
class Person
{
private Mouth mouth;
public Person(Mouth mouth)
{
this.mouth = mouth;
}
/// <summary>
/// 吃飯
/// </summary>
public void Eat()
{
if (mouth == null)
{
throw new NullReferenceException();
}
mouth.OpenMouth();
FillMouthWithFood();
mouth.CloseMouth();
}
private void FillMouthWithFood(){ }
}
class Mouth
{
/// <summary>
/// 張嘴
/// </summary>
public void OpenMouth() { }
/// <summary>
/// 閉嘴
/// </summary>
public void CloseMouth() { }
}
也許有人會(huì)說(shuō),這是再正常不過(guò)的代碼了。但是我們要考慮到,張嘴和閉嘴的動(dòng)作是具體的行為,具體的行為就可能發(fā)生變化。而在這里,高層代碼Person類還依賴于低層代碼,這時(shí)當(dāng)?shù)蛯哟a發(fā)生變化時(shí),高層代碼也就會(huì)隨之發(fā)生變化。而如果隨著層數(shù)的增多,震蕩地劇烈程度也就會(huì)隨之增加。
接下來(lái)我們來(lái)看這樣一個(gè)圖:
那么,我們要怎么樣來(lái)解決這種情況。
2. 抽象,我們談?wù)劤橄?/strong>
這個(gè)詞太時(shí)尚了,當(dāng)然,這個(gè)詞本身也很抽象,那么什么叫做抽象。
我們來(lái)看看《現(xiàn)代漢語(yǔ)詞典》對(duì)這個(gè)詞的解釋:抽象是從許多事物中,舍棄個(gè)別的,非本質(zhì)的屬性,抽出共同的特征而形成的。
那么說(shuō),抽象類和接口都屬于抽象。所以很多書上說(shuō):接口優(yōu)于抽象類,我覺(jué)得這句話實(shí)在是一句非常扯淡的話!接口是抽象,難道抽象類就不是抽象了?明明是兩種完全不同的東西,談何比較?!
面試的時(shí)候,大家也許都被面試過(guò)一個(gè)問(wèn)題就是接口和抽象類的區(qū)別,網(wǎng)上最常見的一種答案就是:接口代表一種Can-Do語(yǔ)義,抽象類是一個(gè)Is-a的語(yǔ)義。這句話不無(wú)道理,我以前也都是這樣來(lái)回答筆試和面試題??墒乾F(xiàn)在我覺(jué)得這句話并不能對(duì)真正的設(shè)計(jì)有任何的幫助。
這么說(shuō)吧,任何方法都是可以被表示成Can-Do的語(yǔ)義,那是不是說(shuō),所有的方法都要被放到接口中,而所有的抽象類都是貧血類,或者說(shuō)所有的抽象類都要實(shí)現(xiàn)接口么?
3. 接口和抽象類的區(qū)別
回顧我們?cè)诘谝稽c(diǎn)種說(shuō)的依賴反轉(zhuǎn)原則,高層不應(yīng)該依賴于低層,那么也就是說(shuō)在設(shè)計(jì)時(shí),不應(yīng)該遵循從低層到高層的設(shè)計(jì)。而應(yīng)該先設(shè)計(jì)整體的業(yè)務(wù)(也就是高層模塊,戰(zhàn)略性內(nèi)容),然后再根據(jù)高層模塊去設(shè)計(jì)低層模塊(也就是具體實(shí)現(xiàn))。而在高層與底層之前也不應(yīng)該直接產(chǎn)生依賴,而應(yīng)該在他們兩層之間搭建一層抽象,這層抽象在我看來(lái)可以說(shuō)叫做“接口”。
那么這里我就提出我對(duì)接口和抽象類的認(rèn)識(shí):接口是從高層需求而來(lái),抽象類是從底層總結(jié)而來(lái)。
我來(lái)解釋一下我這句話,在設(shè)計(jì)一個(gè)高層模塊的時(shí)候,這個(gè)高層模塊不知道低層模塊的細(xì)節(jié),甚至不知道實(shí)現(xiàn)方式。它只知道我需要一個(gè)這個(gè)行為,然后需要一個(gè)這個(gè)行為。比如這個(gè)代碼:
這是一個(gè)高層代碼,比賽中有一項(xiàng)運(yùn)動(dòng)是先跑步,在競(jìng)走,最后是游泳。也許比賽分為男子組和女子組,不同的人走路,跑步方式還不一樣。但是我并不關(guān)心這個(gè)行為的所屬者是誰(shuí),我只要求知道,我需要這個(gè)行為。這個(gè)就是接口的來(lái)源。class Game
{
public void Sport()
{
Run();
Walk();
Swim();
}
}而抽象類則是從底層的實(shí)現(xiàn)中提煉出來(lái)的。所以我們不妨也可以換種說(shuō)法。“接口是一個(gè)面向?qū)ο蠓治雠c設(shè)計(jì)的必然結(jié)果,而抽象類則是重構(gòu)的結(jié)果”。或者我們也可以這樣說(shuō):“先有接口,后有抽象類”。class Game
{
private IRunable runner;
private IWalkable walker;
private ISwimable swimmer;
public Game(IRunable runner,IWalkable walker, ISwimable swimmer)
{
this.runner = runner;
this.walker = walker;
this.swimmer = swimmer;
}
public void Sport()
{
runner.Run();
walker.Walk();
swimmer.Swim();
}
}
interface IRunable
{
void Run();
}
interface IWalkable
{
void Walk();
}
interface ISwimable
{
void Swim();
}本來(lái)想結(jié)束這一節(jié),但是為了希望大家進(jìn)一步理解我的觀點(diǎn),我再補(bǔ)一句,絕對(duì)不應(yīng)該從兩個(gè)類中抽取出接口。
4.自底向上vs.自頂向下
究竟是自底向上還是自頂向下,這兩種究竟哪一種更好,相信每個(gè)人都有自己的觀點(diǎn)。那么我們來(lái)想一下這兩種方法的優(yōu)劣。
自底向上方法關(guān)鍵在于組裝,先設(shè)計(jì)低層,低層不知道高層的業(yè)務(wù),就像搭積木,每個(gè)積木都不是為了最終的建筑物而設(shè)計(jì),而是遵循它自有的形狀。關(guān)鍵在于玩家自己的組裝。
自頂向下方法的關(guān)鍵在于最初業(yè)務(wù)的分析,根據(jù)需求文檔中的業(yè)務(wù),提取出業(yè)務(wù)模型,然后根據(jù)業(yè)務(wù)模型分析出每個(gè)業(yè)務(wù)的行為,然后去分別實(shí)現(xiàn)每個(gè)行為。
自底向上方法的最大優(yōu)點(diǎn)在于底層模塊的重用性,就像積木一樣,可以搭成高樓,也可以改成狗屋。但是劣勢(shì)在于底層的變動(dòng)會(huì)引起高層的級(jí)聯(lián)震蕩,此外,積木設(shè)計(jì)者相當(dāng)于我們的架構(gòu)師,玩家相當(dāng)于我們“套頁(yè)面”的程序員,架構(gòu)師的好壞會(huì)完全直接影響到最終的項(xiàng)目成敗。
自頂向下方法最大的優(yōu)點(diǎn)就在于上面提的,依賴倒置會(huì)保證業(yè)務(wù)的可重用性,此外,不會(huì)讓架構(gòu)師去憑空想象一個(gè)業(yè)務(wù)邏輯,而是根據(jù)需求文檔中的業(yè)務(wù)邏輯去整個(gè)他的業(yè)務(wù)模型。當(dāng)然,劣勢(shì)就在于底層代碼的重用性會(huì)極低。
那么,我們究竟該如何去做呢。這里我只提出自己的觀點(diǎn)。
在軟件工程中有一個(gè)“V”字型模型,具體是做什么的我有些記不清楚了。在這里,我覺(jué)得也可以用V字型模型來(lái)做。
在接到需求文檔的時(shí)候,采用自頂向下的分析方法來(lái)整理出相關(guān)業(yè)務(wù),然后每個(gè)業(yè)務(wù)的大致實(shí)現(xiàn)邏輯,根據(jù)這些實(shí)現(xiàn)邏輯來(lái)抽取去接口,OK。高層模塊已經(jīng)設(shè)計(jì)完了。
接下來(lái),我們來(lái)采用自底向下的方法來(lái)分析。其實(shí)就是我們常說(shuō)的“領(lǐng)域模型”,相信我,單純地用自頂向下的分析方法是很難設(shè)計(jì)出通用的領(lǐng)域模型的。這個(gè)領(lǐng)域模型,就是我們最可重用的模塊。而領(lǐng)域模型的設(shè)計(jì),也正是我們“設(shè)計(jì)模式”的最大應(yīng)用之處。
看到上面的兩段,就是先有接口后有抽象類的典型。
說(shuō)到最后,越說(shuō)越亂了,算了,停下來(lái)吧。。。。。。
聯(lián)系客服