橋式
(The
Bridge Pattern)
概述
這一章我們將會(huì)通過(guò)
Bridge
模式來(lái)繼續(xù)我們?cè)O(shè)計(jì)模式的學(xué)習(xí)。
Bridge
模式比我們以前所講到的
Facade
、
Adapter
、
Strategy
這些模式都更有用,但也更復(fù)雜。
這一章
-
我將給出一個(gè)實(shí)際的例子,并進(jìn)行細(xì)致的講解,來(lái)幫助你學(xué)習(xí)
Bridge
模式。
-
我還會(huì)把
Bridge
模式的功能,實(shí)現(xiàn)方法之類的很關(guān)鍵性的東西通過(guò)一個(gè)表列出來(lái)。
-
我還會(huì)加入一點(diǎn)我自己對(duì)
Bridge
模式的看法。
Bridge
模式簡(jiǎn)介
按照
GoF
的說(shuō)法,我們使用
Bridge
模式的目的是“把抽象從它的具體實(shí)現(xiàn)中分離出來(lái),以使二者可以獨(dú)立變化”。
我還記得我第一次看到這句話時(shí)是多么的驚訝,(原以為會(huì)是多么高級(jí),原來(lái)就是把實(shí)現(xiàn)和抽象分離而已,貌似作者當(dāng)時(shí)很失望)。我很清楚這句話里每一個(gè)單詞的具體含義,然而,我想我還是沒(méi)有理解到這句話。
我知道:
-
“分離”就是說(shuō)使事物的行為和其他事物無(wú)關(guān),或者至少說(shuō)要把他們之間的關(guān)系弄明白。
-
“抽象”是指不同事物在某種概念上的一致性,或者說(shuō)是相關(guān)性。
我以為,要建立抽象的方法就是“實(shí)現(xiàn)”,當(dāng)我看到
GoF
說(shuō)要把抽象從實(shí)現(xiàn)中分離出來(lái)的時(shí)候,我就不能不困惑了。
看起來(lái),我的困惑都是因?yàn)槲艺`解了實(shí)現(xiàn)的含義。這里的“實(shí)現(xiàn)”是指那些抽象類以及其派生類用來(lái)實(shí)現(xiàn)它們自身的對(duì)象。老實(shí)說(shuō),如果一開(kāi)始我就能明白到這一點(diǎn),那肯定要省事很多,然而這個(gè)句子的確很難讓人一下子就明白其真正含義。
如果你現(xiàn)在和我當(dāng)時(shí)一樣對(duì)
Bridge
模式仍然充滿了困惑,沒(méi)關(guān)系,相信通過(guò)后面的講解,你會(huì)對(duì)
Bridge
模式有一個(gè)非常清楚的認(rèn)識(shí);如果你現(xiàn)在就已經(jīng)很清楚
Bridge
模式的目的了的話,那么后面你將會(huì)很輕松。
Bridge
模式可以算是眾多晦澀的模式中的一個(gè),因?yàn)樗δ軓?qiáng)大,應(yīng)用范圍又很廣,還因?yàn)樗奶幚矸绞胶屯ǔS美^承/派生方式處理問(wèn)題有點(diǎn)背道而馳的感覺(jué)。然而,它仍然是一個(gè)很好的例子,一個(gè)遵循設(shè)計(jì)模式兩大要求的例子。它們是:“找到什么是變化的并封裝它”
和 “盡量考慮使用聚合而不是繼承/派生”,隨后,我們將會(huì)看到
Bridge
模式是如何符合這兩點(diǎn)的。
通過(guò)例子學(xué)習(xí)
Bridge
模式
我將通過(guò)從頭開(kāi)始講解一個(gè)例子的方式來(lái)幫助你理解
Bridge
模式的思想和它能做什么。我將從最初的系統(tǒng)要求開(kāi)始講解這個(gè)例子,然后逐步引入
Bridge
模式并把它應(yīng)用到這個(gè)例子當(dāng)中。
也許這個(gè)例子看起來(lái)過(guò)于基礎(chǔ)。但還是請(qǐng)看看本例所討論的一些概念,再想想你所遇到過(guò)的和下面相似的情形:
-
被“抽象”的概念在變化。
-
抽象的實(shí)現(xiàn)方法也在變化。
你將會(huì)發(fā)現(xiàn)這和我們先前討論過(guò)的
CAD/CAM
問(wèn)題有些相似。我將會(huì)逐漸增加這個(gè)例子里的需求,就像我們實(shí)際遇到的那樣,而不是一次就把所有的需求都提出來(lái)。你總不可能在問(wèn)題一開(kāi)始就預(yù)見(jiàn)所有可能的變化吧。
這樣看來(lái),我們的底線就是:在系統(tǒng)的需求沒(méi)有定型之前,盡可能多盡可能早的預(yù)見(jiàn)可能出現(xiàn)的變化。
假設(shè),我接到一個(gè)任務(wù),可以用兩個(gè)不同的程序來(lái)畫(huà)矩形。而且要求,在初始化一個(gè)矩形的時(shí)候,我要知道我是使用第一個(gè)繪圖程序
(DP!)
還是第二個(gè)繪圖程序
(DP2)
。
如圖
10-1
所示,通過(guò)對(duì)角線上的兩個(gè)點(diǎn)來(lái)定義一個(gè)矩形。這兩個(gè)繪圖程序的不同之處也已經(jīng)總結(jié)在表
10-1
里。
圖
10-1
定位一個(gè)矩形
表
10-1
兩個(gè)繪圖程序的不同之處
-
|
DP1
|
DP2
|
畫(huà)線
|
draw_a_line(
x1 , y1 , x2 , y2 )
|
drawline(
x1, x2, y1, y2)
|
畫(huà)圓
|
draw_a_circle
(x , y , r )
|
drawcircle(
x, y, r)
|
根據(jù)分析,我不希望繪制矩形的代碼知道具體是使用的
DP1
還是
DP2
。但是,前面又有要求說(shuō)在矩形初始化的時(shí)候就要明確使用的到底是
DP1
還是
DP2
,這樣的話,我可以通過(guò)用兩種矩形來(lái)解決:一種用
DP1
,一種用
DP2
。它們都有一個(gè)
draw()
方法,但是各自的實(shí)現(xiàn)方式不同。如圖
10-2
所示。
圖
10-2
Design for rectangles and drawing programs (DP1 and DP2).
由于這兩種矩形的唯一不同之處就在于他們使用的繪圖程序,所以,通過(guò)抽象類
Rectangle
,讓這兩種矩形分別實(shí)現(xiàn)
drawLine()
方法就可以了。
V1Rectangle
通過(guò)包含一個(gè)
DP1
的對(duì)象,并調(diào)用該對(duì)象的
draw_a_line()
方法來(lái)實(shí)現(xiàn),
V2Rectangle
則通過(guò)包含一個(gè)
DP2
的對(duì)象,并調(diào)用該對(duì)象的
drawLine()
方法來(lái)實(shí)現(xiàn)。這樣,在初始化
Rectangle
的時(shí)候,我就不必再理會(huì)這些不同了。
例
10-1
Java
代碼片段
abstract
?
public
?
class
?Rectangle?{
????
private
?
double
?_x1,?_y1,?_x2,?_y2;
????
public
?Rectangle?(
double
?x1,?
double
?y1,?
double
?x2,?
double
?y2){
????????_x1?
=
?x1;
????????_y1?
=
?y1;
????????_x2?
=
?x2;
????????_y2?
=
?y2;
????}
????
public
?
void
?draw?(){
????????drawLine(_x1,?_y1,?_x2,?_y1);
????????drawLine(_x2,?_y1,?_x2,?_y2);
????????drawLine(_x2,?_y2,?_x1,?_y2);
????????drawLine(_x1,?_y2,?_x1,?_y1);
????}
????
abstract
?
protected
?
void
?drawLine?(
double
?x1,?
double
?y1,?
double
?x2,?
double
?y2);
}
現(xiàn)在我們已經(jīng)完成了上面的代碼以及
V1Rectangle
和
V2Rectangle
的代碼。但是,我又被要求程序能夠支持另一種除了矩形以外的形狀,圓。而且,同樣要求,保存這些“圓”的容器不需要知道它保存的到底是圓
(Circle)
還是矩形
(Rectangle)
。
看起來(lái),我可以在我現(xiàn)有的實(shí)現(xiàn)基礎(chǔ)上擴(kuò)展一下就可以了。我加入一個(gè)新的
Shape
類,讓
Rectangle
和
Circle
都去繼承它。這樣,客戶端對(duì)象可以只保存
Shape
類的對(duì)象,而不用起考慮到底具體是什么類型的
Shape(Rectangle
或是
Circle)
。
在一個(gè)面向?qū)ο蠓治龅男率值难劾铮美^承來(lái)實(shí)現(xiàn)這些要求,看起來(lái)是很自然的事情。例如,我可以像圖
10-2
所演示的那樣做。通過(guò)繼承再繼承,每個(gè)形狀都分別派生出使用
DP1
和
DP2
的類,最后,得到圖
10-3
這樣的結(jié)果。
Figure
10-3 A straightforward approach: implementing two shapes and two
drawing programs.
我用實(shí)現(xiàn)
Rectangle
的方法來(lái)實(shí)現(xiàn)
Circle
。然而,這次,實(shí)現(xiàn)
draw()
這個(gè)方法的時(shí)候是用
drawCircle()
而不再是
Rectangle
里的
drawLine()
。
例
10-2
Java
代碼片段
public
?
abstract
?
class
?Shape?{
????
abstract
?
public
?
void
?draw?();
}
//
?the?only?change?to?Rectangle?is
abstract
?
class
?Rectangle?
extends
?Shape?{
????
}
//
V1Rectangle?and?V2Rectangle?don't?change
public
?
abstract
?
class
?Circle?
extends
?Shape?{
????
protected
?
double
?_x,?_y,?_r;
????
public
?Circle?(
double
?x,?
double
?y,?
double
?r){
????????_x?
=
?x;
????????_y?
=
?y;
????????_r?
=
?r;
????}
????
public
?
void
?draw?(){
????????drawCircle?();
????}
????
abstract
?
protected
?
void
?drawCircle();
}
public
?
class
?V1Circle?
extends
?Circle?{
????
public
?V1Circle?(
double
?x,?
double
?y,?
double
?r){
????????
super
(?x,?y,?r);
????}
????
protected
?
void
?drawCircle?(){
????????DP1.draw_a_circle?(_x,?_y,?_r);
????}
}
public
?
class
?V2Circle?
extends
?Circle?{
????
public
?V2Cricle?(
double
?x,?
double
?y,?
double
?r){
????????
super
(?x,?y,?r);
????}
????
protected
?
void
?drawCircle?(){
????????DP2.drawCircle(?_x,?_y,?_r);
????}
}
我們來(lái)仔細(xì)看看這個(gè)例子。看看
draw()
和
V1Rectangle
都做了些什么。
-
Rectangle
的
draw()
方法和先前的一樣
(
都根據(jù)需要調(diào)用
drawLine()
四次
)
。
-
drawLine()
通過(guò)調(diào)用
DP1
的
draw_a_line()
來(lái)實(shí)現(xiàn)。
其執(zhí)行的步驟如圖
10-4
所示。
Figure
10-4 Sequence Diagram when have a V1Rectangle.
雖然,在類圖上看起來(lái),程序里有很多的對(duì)象,但是實(shí)際上,我只需要處理三個(gè)對(duì)象。如圖
10-5
所示。
Figure
10-5 The Objects present.
-
Client
對(duì)象使用到矩形。
-
V1Rectangle
對(duì)象。
-
繪圖程序
DP1
。
當(dāng)
client
對(duì)象傳遞一個(gè)消息到
V1Rectangle
的對(duì)象
(
也就是
myRectangle)
,執(zhí)行其中的
draw()
方法的時(shí)候,它通過(guò)圖中的步驟
2
到步驟
9
來(lái)完成調(diào)用
draw()
方法的全過(guò)程。
不幸的是,這種方法又引起了新的問(wèn)題。回過(guò)頭來(lái)看看圖
10-3
,仔細(xì)看看第三行的類,想想下面的問(wèn)題:
-
這一行的類代表了四種不同類型的
Shape
。
-
如果我現(xiàn)在又有了一個(gè)新的繪圖程序,也就是說(shuō),在實(shí)現(xiàn)上又有了一個(gè)新的變化。這樣,我是不是該有
6
個(gè)類了?
(2
個(gè)
Shape
再乘上
3
個(gè)繪圖程序。
)
-
再來(lái),如果在前面基礎(chǔ)上,我現(xiàn)在又要實(shí)現(xiàn)一個(gè)新的形狀,比方說(shuō),再來(lái)個(gè)橢圓,完了,現(xiàn)在,我該有
3
*
3
=
9
個(gè)類了。
類的數(shù)量就和滾雪球一樣,越來(lái)越多。這種情況的出現(xiàn)是因?yàn)槌橄?/font>
(
各種各樣的
Shape)
和實(shí)現(xiàn)
(
多個(gè)繪圖程序
)
他們很緊密地耦合在一起!每種確切的形狀都要知道它所使用的繪圖程序的具體類型。看樣子,要避免這種類在數(shù)量上的爆炸式的增長(zhǎng),我得需要一個(gè)能夠把抽象和實(shí)現(xiàn)分離看來(lái)的方法。讓他們可以獨(dú)立的發(fā)生變化。這樣,類的數(shù)量就可以呈現(xiàn)出線性的增長(zhǎng),而不是近指數(shù)形式的增長(zhǎng)了。如圖
10-6
所示。
Figure
10-6 The Bridge pattern separate variations in abstraction and
implementation.
在具體講解處理方法和
Bridge
模式之前,我還想講點(diǎn)其他的問(wèn)題。
讓我們?cè)俅位剡^(guò)頭去看看圖
10-3
,自問(wèn)一下:這個(gè)設(shè)計(jì)的缺點(diǎn)在哪里?
-
這個(gè)設(shè)計(jì)看起來(lái)是不是有點(diǎn)累贅?
-
設(shè)計(jì)里的對(duì)象間是結(jié)合是否緊密呢?
-
對(duì)象是不是很緊密的耦合在一起呢?
這樣的代碼,你愿意去維護(hù)嗎?
-
濫用繼承
|
作為一個(gè)新手,我曾經(jīng)趨向于用“特殊化”的方式來(lái)解決問(wèn)題,也就是利用繼承。我曾經(jīng)很喜歡使用繼承這種方式,因?yàn)樗莻€(gè)新穎有功能強(qiáng)大的方法。我總是盡可能地使用繼承,這樣的情況在新手里經(jīng)常出現(xiàn),但是,這種做法是很天真的:有了“繼承”這把“榔頭”,見(jiàn)到啥玩意都象顆釘子,一榔頭就下去了。然而,令人感到遺憾的是,在學(xué)習(xí)面向?qū)ο笤O(shè)計(jì)的過(guò)程中,人們總是受著“特殊化”的熏陶,總是被教導(dǎo)著通過(guò)特殊化來(lái)處理一切變化,無(wú)休止的從基類派生出新類。由于過(guò)度地注重“是什么”,而使得很多設(shè)計(jì)都是建立在一些很固化的繼承鏈上的,這些設(shè)計(jì)在一開(kāi)始的時(shí)候可以很好的運(yùn)行,但是,隨著時(shí)間的推移,這些設(shè)計(jì)將會(huì)變得非常難以維護(hù)
(
正如在第九章,”策略模式”里面所講到的那樣
)
。
即使是在我經(jīng)驗(yàn)已經(jīng)比較豐富的時(shí)候,我還是會(huì)把這種基于繼承的方法過(guò)度的應(yīng)用到設(shè)計(jì)當(dāng)中,只管類”是什么”,而并沒(méi)有意識(shí)到我這么做,將會(huì)使整個(gè)設(shè)計(jì)在結(jié)構(gòu)上變得多么的復(fù)雜!
而,是設(shè)計(jì)模式的思維方法使我擺脫了這些。我學(xué)會(huì)用我的對(duì)象具有什么要嗯的職能的方式去思考問(wèn)題,而不再是單從對(duì)象的結(jié)構(gòu)上去思考問(wèn)題。
真正有經(jīng)驗(yàn)的面向?qū)ο蟮脑O(shè)計(jì)師,他們都會(huì)很有選擇性地使用繼承。而學(xué)習(xí)設(shè)計(jì)模式,將會(huì)使你更快的學(xué)到這些。它將帶給你一個(gè)從把每個(gè)變體特殊化到學(xué)會(huì)使用聚合的轉(zhuǎn)變。
|
最初,我看到這些問(wèn)題的出現(xiàn),我曾以為這一切都是因?yàn)槲业睦^承方式使用得不對(duì)而引起的。于是,我試著改進(jìn)了先前的繼承鏈,把它變得像圖
10-7
那樣。
Figure
10-7 An alternative implementation.
我還是有四個(gè)類,但是,通過(guò)首先就按繪圖程序的不同而區(qū)分類的方式,減少了很多
DP1
和
DP2
之間的冗余內(nèi)容。
然而,我還是不能排除兩種
Rectangle
和兩種
Circle
之間的冗余,每種
Rectangle
和每種的
Circle
都有相同的
draw()
方法。
不管怎么說(shuō),先前的那種類數(shù)量上的爆炸式的增長(zhǎng),在這里又出現(xiàn)了。
新的設(shè)計(jì)方案的執(zhí)行過(guò)程如圖
10-8
所示。
Figure
10-8 Sequence diagram for new approach.
雖然這個(gè)方案比最初的那個(gè)要好那么一點(diǎn)點(diǎn),但是還是存在類數(shù)量將會(huì)過(guò)大的問(wèn)題,而且對(duì)象間的耦合也存在問(wèn)題。
也就是說(shuō),我還是不想維護(hù)這樣的代碼,一定還會(huì)有更好的方案的!
-
替代原有設(shè)計(jì)方案
|
雖然我使用的替代方案并比我的原始方案優(yōu)秀多少,但是,我還是要指出來(lái)的就是,在原有方案上尋找替代方案是很重要的。太多的程序員都是抱著最開(kāi)始的東西不放。當(dāng)然了,我不是說(shuō)要去深入地考慮所有可替代的方案
(
真是這樣的話,就永遠(yuǎn)停留在分析上了
)
。然而,在遇到困難的時(shí)候,回過(guò)頭去看看是否可以在原始方案里就能夠解決這些困難。這,是很重要的。事實(shí)上,我們只是退后一步而已,明知道原來(lái)的設(shè)計(jì)方案存在這樣那樣的問(wèn)題,退回來(lái)看看有沒(méi)有什么辦法可以補(bǔ)救。正是這樣,使我領(lǐng)悟到設(shè)計(jì)模式的魅力。
|
關(guān)于使用設(shè)計(jì)模式的一些觀察
當(dāng)人們開(kāi)始關(guān)注設(shè)計(jì)模式的時(shí)候,通常他們只關(guān)心的是設(shè)計(jì)模式所提供的解決方法。這看起來(lái)不無(wú)道理,因?yàn)樵O(shè)計(jì)模式不是一直都被說(shuō)成是能夠提供一些好的辦法來(lái)解決我們手頭上的難題么?
然而,這樣的看法是站在我們遇到一個(gè)難題的基礎(chǔ)上提出來(lái)的。實(shí)際上,在運(yùn)用設(shè)計(jì)模式到設(shè)計(jì)中之間,你應(yīng)該首先明白你的設(shè)計(jì)將會(huì)做什么。正確的做法應(yīng)該是考慮一下你可以在設(shè)計(jì)中的哪些地方用到設(shè)計(jì)模式,這將告訴你要做什么,而不是什么時(shí)候我要用設(shè)計(jì)模式了,也不是為什么我要去用設(shè)計(jì)模式。
我發(fā)現(xiàn),關(guān)心一下一個(gè)設(shè)計(jì)模式將會(huì)處理的問(wèn)題的方式很有用。這樣,我就會(huì)知道什么時(shí)候,為什么要使用到這個(gè)模式。
當(dāng)你的一個(gè)抽象有很多種實(shí)現(xiàn)方法的時(shí)候,你就會(huì)發(fā)現(xiàn)
Bridge
模式的妙用。它可以讓你的抽象和實(shí)現(xiàn)各自獨(dú)立的發(fā)生變化。
我們一直在討論的這個(gè)繪圖的問(wèn)題就很符合
Bridge
模式的應(yīng)用場(chǎng)合。因此,我知道,我應(yīng)該在這個(gè)設(shè)計(jì)里用到
Bridge
模式了,雖然,到目前位置,我還不知道我該怎么做。讓抽象和實(shí)現(xiàn)可以實(shí)現(xiàn)獨(dú)立的變化意味著我可以任意的添加新的抽象而不用去修改原有的具體實(shí)現(xiàn)。
而當(dāng)前的方案還不允許這樣的獨(dú)立變化。
有一點(diǎn)很重要,就是即使在不知道具體怎么實(shí)現(xiàn)
Bridge
模式的情況下,你還是可以肯定
Bridge
模式在這個(gè)設(shè)計(jì)中是很有用的。以后你就會(huì)發(fā)現(xiàn),這種情況在設(shè)計(jì)模式領(lǐng)域里很常見(jiàn),那就是,你可以確定你應(yīng)該把某個(gè)模式應(yīng)用到你的設(shè)計(jì)當(dāng)中,盡管,你還并不知道該怎么去實(shí)現(xiàn)這個(gè)模式。
Bridge
模式的引出
相信你已經(jīng)很清楚我們所要處理的問(wèn)題了,那么,現(xiàn)在我們就用
Bridge
模式來(lái)解決我們的問(wèn)題。這個(gè)過(guò)程將幫助你更加深入地理解這個(gè)復(fù)雜而強(qiáng)大的
Bridge
模式。
我們還是要遵循那兩個(gè)“原則”:
-
找到什么是變化的并封裝它。
-
盡量使用聚合而不是繼承。
以前,開(kāi)發(fā)人員總是依靠大量的繼承來(lái)處理程序中的變化,而第二個(gè)“原則”告訴我要盡可能的使用聚合。使用聚合的目的就是讓程序可以在單獨(dú)的類里面包含它自己的變化,這樣,今后有新變化出現(xiàn)的時(shí)候就可以通過(guò)添加新的類來(lái)包含這個(gè)變化,而不會(huì)對(duì)原先的類造成影響。要達(dá)到這個(gè)目的,我們就需要把所有的變化都包含到它們共同的抽象類里面,然后,我們?cè)偃タ紤]抽象類之間的聯(lián)系,而不再考慮抽象類的具體實(shí)現(xiàn)。
-
再說(shuō)封裝
|
很多面向?qū)ο蟪绦蛟O(shè)計(jì)的開(kāi)發(fā)人員都知道“封裝”就是數(shù)據(jù)隱藏。其實(shí),這是一個(gè)很狹隘的定義!實(shí)際上,“封裝”除了數(shù)據(jù)隱藏以外,還有很多其他的用法。如果你再回過(guò)頭去看看圖
7-2
,你會(huì)在里面發(fā)現(xiàn)“封裝”可以操縱很多個(gè)層次。當(dāng)然,那里,封裝的目的就是為每個(gè)
Shape
做數(shù)據(jù)隱藏。然而,要注意的是,
Client
對(duì)象并不知道它所處理的
Shape
的具體類型
(
到底是
Rectangle
還是
Circle)
。因此,那個(gè)具體被
Client
所處理的類就通過(guò)
Shape
而實(shí)現(xiàn)了隱藏
(
也就是封裝
)
,這也就是
GoF
在說(shuō)到“找到什么是變化的并封裝它”的時(shí)候所指的“封裝”,讓抽象類
(Shape)
出面去和
Client
對(duì)象交互,具體的
Rectangle
、
Circle
這些類都“躲”在了
Shape
類的后面,
Client
只知道自己在和
Shape
交互,其他一無(wú)所知。
|
現(xiàn)在我具體討論一下本章里一直在講的這個(gè)繪圖的例子。
首先,找出什么是變化的。在這個(gè)例子里,也就是不同的形狀和不同的繪圖程序。而這些不同的形狀、不同的繪圖程序在概念上,分屬于“形狀”和“繪圖程序”,這樣,我就可以用
10-9
所示的類來(lái)代表這些不同的形狀和程序
(
注意圖里面的類名是斜體的,這表示他們都是抽象類
)
。
Figure
10-9 What is varying?
這樣,我打算用
Shape
來(lái)封裝這個(gè)例子里的所有的不同的形狀,而代表這些不同的形狀的類將必須自己能夠完成他本身的繪制工作。
Drawing
類的對(duì)象將僅僅負(fù)責(zé)完成“線”和“圓”等具體線條的繪制工作。這些具體的繪制,都將通過(guò)在類里定義的一些方法來(lái)完成。
現(xiàn)在我們已經(jīng)很抽象地表示出“形狀”和“繪圖程序”了,下一步,就是怎么表示具體的形狀和具體的繪圖程序的問(wèn)題了。我有的“形狀”是矩形和圓,那么,我就從
Shape
里派生出
Rectangle
和
Circle
來(lái)分別代表矩形和圓。“繪圖程序”我必須使用已有的
DP1
和
DP2
,但是我又不能直接使用它們,所以,我可以從
Drawing
派生出
V1Drawing
和
V2Drawing
,讓他們分別去使用
DP1
和
DP2
。這樣,就得到如圖
10-10
所示的類圖。
Figure
10-10 Represent the variations.
到目前為止,這一切看起來(lái)還是有點(diǎn)抽象。我只說(shuō)了
V1Drawing
和
V2Drawing
要分別對(duì)應(yīng)地使用到
DP1
和
DP2
,但是我還沒(méi)有說(shuō)怎么用。我只是把整個(gè)例子中存在的一些變化找了出來(lái)而已。
有了這兩組類,我現(xiàn)在要關(guān)心的就是它們之間怎么彼此關(guān)聯(lián)起來(lái)的問(wèn)題了。我不想再出現(xiàn)一組用同樣繼承的方式出現(xiàn)的類,這樣的結(jié)果我已經(jīng)很清楚了
(
再回過(guò)頭去看看圖
10-3
和
10-7
,加深點(diǎn)印象
)
。相反地,我在想,我是不是可以通過(guò)用一組類使用另一組的方式來(lái)完成
(
這不就是用聚合了嗎?
)
。現(xiàn)在的問(wèn)題是,兩組類,到底誰(shuí)用誰(shuí)?
很明顯的兩種可能性:要么在
Shape
里用到
Drawing
,要么反過(guò)來(lái),
Drawing
里用到
Shape
。
我們先來(lái)看看后面這種
Drawing
里使用
Shape
的情況,如果繪圖程序直接使用到具體的形狀,那么這些程序就必須知道該怎么去完成這些具體形狀的繪制。但是,這些形狀僅僅只知道如何完成“線”和“圓”這些簡(jiǎn)單線條的繪制,這,超出了它們的能力范圍。
那么,再看看前面的那種情況。如果在
Shape
里用
Drawing
會(huì)怎么樣?
Shape
將沒(méi)有必要知道它所使用的
Drawing
的確切類型,因?yàn)椋覀円呀?jīng)用
Drawing
封裝了
V1Drawing
和
V2Drawing
。
這樣看起來(lái)不錯(cuò),就像圖
10-11
所示的那樣。
Figure
10-11 Tie the classes together.
在這個(gè)設(shè)計(jì)里,
Shape
通過(guò)
Drawing
來(lái)完成自己的繪制。這里,我略去了
V1Drawing
使用
DP1
和
V2Drawing
使用
DP2
的細(xì)節(jié),在圖
10-12
里,我把這些都表示了出來(lái)還加入了一些
protected
的方法。
Figure
10-12 Expanding the design.
圖
10-13
所示的是把
Shape(
抽象
)
和
Drawing(
實(shí)現(xiàn)
)
分離開(kāi)來(lái)。
Figure
10-13 Class diagram illustrating separation of abstraction and
implementation.
-
一個(gè)規(guī)則,一個(gè)地方
|
我們必須遵循的一個(gè)非常重要的實(shí)現(xiàn)策略就是,一個(gè)規(guī)則僅僅應(yīng)用到一個(gè)地方。也就是說(shuō),如果你有一個(gè)處理事務(wù)的規(guī)則,這個(gè)規(guī)則就僅用一次就好。很顯然這會(huì)導(dǎo)致設(shè)計(jì)里出現(xiàn)很多的小方法。但是,這么做你可以避免一些預(yù)想不到的問(wèn)題的出現(xiàn),可以避免因?yàn)橐?guī)則相似,而復(fù)制原有代碼再修改滿足新規(guī)則的情況,而這么做的代價(jià)是很小的。
雖然
Rectangle
或者
draw()
方法都可以直接操縱
Shape
的任意的
Drawing
對(duì)象,我可以通過(guò)使用下面的“一個(gè)規(guī)則,一個(gè)地方”的策略來(lái)改進(jìn)這點(diǎn)。我讓
Shape
的
drawLine()
方法來(lái)調(diào)用
Drawing
對(duì)象的
drawLine()
。
當(dāng)然,凡事也不是那么的絕對(duì)。如果,我認(rèn)為某個(gè)地方應(yīng)該一直遵守某種規(guī)則的話,就像這個(gè)例子一樣。我的
Shape
類有個(gè)
drawLine()
,因?yàn)樗碇乙?/font>
Drawing
來(lái)繪制一條直線;我同樣可以用這樣的方式創(chuàng)建一個(gè)
drawCircle()
,因?yàn)樗碇?/font>
Drawing
繪制一個(gè)圓。
|
站在各個(gè)類里面的具體方法
(method)
的角度來(lái)看,這和基于繼承的設(shè)計(jì)方案
(
如圖
10-3)
沒(méi)什么不同。如果非要說(shuō)有什么不同,那就是現(xiàn)在這些方法存在的位置不同,他們不再是在同一個(gè)類里面,而是被分散到多個(gè)類里面。
在這一章一開(kāi)始我就說(shuō)了,我對(duì)
Bridge
模式的困惑是源于我對(duì)“實(shí)現(xiàn)”的誤解。我以為“實(shí)現(xiàn)”指的是怎么實(shí)現(xiàn)一個(gè)具體的抽象。
是
bridge
模式讓我意識(shí)到,應(yīng)該把“實(shí)現(xiàn)”看作是一個(gè)對(duì)象以外的東西,是一個(gè)為對(duì)象所用的東西。用這樣的方式著手設(shè)計(jì),在不同的繼承鏈里包含各自的變化。圖
10-13
左側(cè)的繼承鏈包含了抽象的變化,右側(cè)的繼承鏈包含的則是我如何實(shí)現(xiàn)這些抽象的變化。這和我們說(shuō)要“盡量使用聚合而不是繼承”的原則相一致。
雖然在整個(gè)設(shè)計(jì)里有不少的類,但是,你只要牢記一點(diǎn),你一次要處理的就只有三個(gè)對(duì)象,這樣你的思路就會(huì)很清晰。
Figure
10-14 There are only three objects at a time.
Example
10-3 Java Code Fragments
public
?
class
?Client?{
????
public
?
static
?
void
?main(String?args[]){
????????Shape?myShapes[];
????????Factory?myFactory?
=
?
new
?Factory();
????????
????????
//
get?rectangles?form?some?other?source
????????myShapes?
=
?myFactory.getShapes();
????????
for
?(Shape?shape?:?myShapes){
????????????shape.draw();
????????}
????}
}
abstract
?
public
?
class
?Shape?{
????
protected
?Drawing?myDrawing;
????
abstract
?
public
?
void
?draw();
????
????Shape?(Drawing?drawing){
????????myDrawing?
=
?drawing;
????}
????
protected
?
void
?drawLine?(
double
?x1,?
double
?y1,?
double
?x2,?
double
?y2){
????????myDrawing.drawLine(x1,?y1,?x2,?y2);
????}
????
protected
?
void
?drawCircle?(
double
?x,?
double
?y,?
double
?r){
????????myDrawing.drawCircle(x,?y,?r);
????}
}
public
?
class
?Rectangle?
extends
?Shape?{
????
private
?
double
?_x1,?_y1,?_x2,?_y2;
????
public
?Rectangle?(Drawing?dp,?
double
?x1,?
double
?y1,?
double
?x2,?
double
?y2){
????????
super
(?dp?);
????????_x1?
=
?x1;
????????_y1?
=
?y1;
????????_x2?
=
?x2;
????????_y2?
=
?y2;
????}
????
public
?
void
?draw(){
????????drawLine(?_x1,?_y1,?_x2,?_y1);
????????drawLine(?_x2,?_y1,?_x2,?_y2);
????????drawLine(?_x2,?_y2,?_x1,?_y2);
????????drawLine(?_x1,?_y2,?_x1,?_y1);
????}
????
protected
?
void
?drawLine(
double
?x1,?
double
?y1,?
double
?x2,?
double
?y2){
????????myDrawing.drawLine(?x1,?y1,?x2,?y2);
????}
}
public
?
class
?Circle?
extends
?Shape?{
????
private
?
double
?_x,?_y,?_r;
????
public
?Circle?(Drawing?dp,?
double
?x,?
double
?y,?
double
?r){
????????
super
(dp);
????????_x?
=
?x;
????????_y?
=
?y;
????????_r?
=
?r;
????}
????
public
?
void
?draw()?{
????????myDrawing.drawCircle?(_x,?_y,?_r);
????}
}
public
?
abstract
?
class
?Drawing?{
????
abstract
?
public
?
void
?drawLine?(
double
?x1,?
double
?y1,?
double
?x2,?
double
?y2);
????
abstract
?
public
?
void
?drawCircle?(
double
?x,?
double
?y,?
double
?r);
}
public
?
class
?V1Drawing?
extends
?Drawing?{
????
public
?
void
?drawLine?(?
double
?x1,?
double
?y1,?
double
?x2,?
double
?y2){
????????DP1.draw_a_line?(x1,?y1,?x2,?y2);
????}
????
public
?
void
?drawCircle?(
double
?x,?
double
?y,?
double
?r){
????????DP1.draw_a_circle?(x,?y,?r);
????}
}
public
?
class
?V2Drawing?
extends
?Drawing?{
????
public
?
void
?drawLine?(?
double
?x1,?
double
?y1,?
double
?x2,?
double
?y2){
????????DP2.drawLine?(x1,?y1,?x2,?y2);
????}
????
public
?
void
?drawCircle?(
double
?x,?
double
?y,?
double
?r){
????????DP2.drawCircle?(x,?y,?r);
????}
}
回顧
Bridge
模式
Bridge
模式主要功能
|
目標(biāo)
|
把具體實(shí)現(xiàn)從使用該實(shí)現(xiàn)的對(duì)象中分離出來(lái)。
|
環(huán)境
|
(
概念
)
抽象類的派生類需要使用多個(gè)實(shí)現(xiàn)方法而又要避免類的數(shù)量的過(guò)多。
|
解決方式
|
給所有實(shí)現(xiàn)定義一個(gè)公共接口并讓
(
概念
)
抽象的派生類使用它。
|
參與方式
|
Abstraction
給要實(shí)現(xiàn)的對(duì)象定義一個(gè)接口。
Implementor
給具體的實(shí)現(xiàn)定義接口,
Abstraction
的派生類使用
Implementor
的派生類而不管到底是用的哪一個(gè)
ConcreteImplementor
。
|
結(jié)果
|
把具體實(shí)現(xiàn)從使用該實(shí)現(xiàn)的對(duì)象中分離出來(lái)的方法增加了程序的靈活性,客戶端對(duì)象不會(huì)知道實(shí)現(xiàn)的確切類型。
|
執(zhí)行方式
|
-
把所有實(shí)現(xiàn)封裝在一個(gè)抽象類里。
-
在要實(shí)現(xiàn)的
(
概念
)
抽象里包含上面的抽象類。
|
Figure
10-15 Generic structure of the Bridge pattern.
|
posted on 2006-11-18 14:35
xiaosilent 閱讀(1890)
評(píng)論(2) 編輯 收藏 所屬分類:
設(shè)計(jì)模式