侯捷《C++/OOP/GP/DP》講座心得
?????????????????
??????????????????????????????????????????????????????????????????????????????????????????????
———
作者:
naven
?
???
很高興侯捷老師又來(lái)公司了,給我們上了四天非常生動(dòng)的技術(shù)講座,受益匪淺,現(xiàn)在我簡(jiǎn)要介紹一下我的學(xué)習(xí)心得,與大家分享。這次講座主要集中在《
C++/OOP/GP/DP
》主題,針對(duì)有一些編程基礎(chǔ)的工程師,對(duì)一些常用的代碼和設(shè)計(jì)做了非常通俗易懂的剖析,非常有幫助。當(dāng)然更深入的理解還需要結(jié)合多種技術(shù)名著來(lái)學(xué)習(xí),我結(jié)合我的理解以及自己的學(xué)習(xí)和開(kāi)發(fā)的經(jīng)驗(yàn)介紹一下
C++/OO/Template
以及
Design Pattern
的理會(huì),考慮到講座的性質(zhì),我并不直述本次講座的內(nèi)容,歡迎批評(píng)指正
J
?
???
侯捷老師的講座基本是講述他多年來(lái)在
C++
領(lǐng)域的研究成果,基本大部分都可以在他的書(shū)籍和網(wǎng)站上能讀到,但是考慮到最近幾年軟件技術(shù)的蓬勃發(fā)展,如
Design Pattern
的更廣泛應(yīng)用,又有許多心得,基本上是較為泛的基礎(chǔ)的層面,并結(jié)合實(shí)際代碼和應(yīng)用,對(duì)實(shí)際項(xiàng)目開(kāi)發(fā)非常有益。下面我逐個(gè)主題泛泛地講一遍。
?
???
面向?qū)ο笾械暮铣桑?/span>
Composition
)和繼承(
Inheritance
)關(guān)系
?
???
通常擴(kuò)展一個(gè)類的功能主要有兩種方式,一種是大家很熟悉的繼承(
inheritance
),另一種就是合成(
composition
),很多初學(xué)
OO
(面向?qū)ο螅┎⒂幸恍┙?jīng)驗(yàn)都很容易搞混這個(gè)的區(qū)別,其實(shí)很簡(jiǎn)單,繼承是解決
Is-a
的問(wèn)題,而合成是解決
Has-a
的問(wèn)題。比如說(shuō)小鳥(niǎo)有兩個(gè)翅膀,就是合成,而鳥(niǎo)是一種飛禽,就是繼承了,設(shè)計(jì)一個(gè)“小鳥(niǎo)”的類,它繼承自”飛禽”,就具有“飛”的特性,但要用合成的方法“包含”一個(gè)“翅膀”的類才具有真正“飛”的功能。
???
別看這兩個(gè)定義很簡(jiǎn)單,其實(shí)很多人都犯過(guò)錯(cuò)誤,包括
Java
類庫(kù)的設(shè)計(jì)者,他們就把
Properties
直接“繼承”自
Hashtable
了,這里其實(shí)應(yīng)該用“合成”。
?
???
講到合成,就應(yīng)該說(shuō)說(shuō)聚合(
Aggregation
),它是描述整體和局部的關(guān)系,合成其實(shí)是一種“強(qiáng)烈”的聚合,它與局部具有相同的生命周期,“容納”局部的“對(duì)象”,而聚合只是“容納”局部的一個(gè)“指針”。比如說(shuō),人和腦袋就是合成,而汽車(chē)與發(fā)動(dòng)機(jī)就是聚合,改裝汽車(chē)可以任意替換更好的發(fā)動(dòng)機(jī),而人的腦袋就不行(目前是這樣:)
???
聚合在
UML
中是以空心棱形的箭頭表示,合成是以實(shí)心棱形的箭頭表示。
?
???
還有一種關(guān)系叫委托(
Delegation
),委托是一種讓合成(
composition
)變得像繼承(
inheritance
)的復(fù)用能力一樣強(qiáng)大的方式。(
a way of making composition as powerful for reuse as inheritance [Lie86, JZ91]
)在委托中,兩個(gè)對(duì)象在處理一個(gè)請(qǐng)求的時(shí)候發(fā)生關(guān)聯(lián):一個(gè)接收的對(duì)象委派操作給它的委托對(duì)象。這跟子類(
subclass
)延遲請(qǐng)求(
deferring requests
)給它的父類(
parent class
)來(lái)實(shí)現(xiàn)類似。但是在繼承里,一個(gè)被繼承的操作(
inherited operation
)通過(guò)
this
成員變量能夠經(jīng)常引用到那個(gè)接收的對(duì)象。為了在委托里達(dá)到同樣的效果,接受者傳遞它自己給它的委托者,以便被委托的操作能夠引用到這個(gè)接收者。
?
???
再說(shuō)一下繼承(
Inheritance
),它是將基類(
base-class
)所有一切(包括
private
)都繼承下來(lái),所以假如你想實(shí)現(xiàn)一個(gè)新的類,只想繼承一部分,就用合成(
Composition
)別用繼承。或者更進(jìn)一步來(lái)講,如果你想改造一個(gè)類,想改造一些接口(
interface
),也建議用合成,通過(guò)轉(zhuǎn)調(diào)內(nèi)部對(duì)象的方法實(shí)現(xiàn),別用虛函數(shù)(
virtual function
)。這是非常符合最基本的
OCP
設(shè)計(jì)原則(
Open-Closed Principle
,開(kāi)閉原則)的方式了。
?
???
類的構(gòu)造(
Constructor
)和析構(gòu)(
Destructor
)
?
???
類的構(gòu)造和析構(gòu)是最基礎(chǔ)的知識(shí)了,任何一個(gè)類的對(duì)象產(chǎn)生和銷(xiāo)毀都必須有這兩個(gè)步驟,但是它們是如何工作的,編譯器是如何制造缺省的
ctor
和
dtor
的,估計(jì)少有人關(guān)注了。
???
一個(gè)類的對(duì)象的產(chǎn)生,會(huì)依次從它最里面的類開(kāi)始構(gòu)造,同一個(gè)類會(huì)跟據(jù)內(nèi)部類成員定義的順序依次構(gòu)造。類對(duì)象的銷(xiāo)毀的過(guò)程則相反。基類的構(gòu)造器會(huì)在用戶定義的
ctor
之前調(diào)用,基類的析構(gòu)則是在用戶定義的
dtor
之后進(jìn)行。熟悉這些過(guò)程,非常有利于設(shè)計(jì)出優(yōu)秀的類庫(kù),也不容易出現(xiàn)內(nèi)存泄露和資源耗盡等問(wèn)題。下面舉個(gè)例子更容易理解:
?
??? class A { public: A(); ~A(); };
??? class B { public: B(); ~B(); };
??? class C { public: C(); ~C(); };
??? class D : public A, B {
??????? public: D() { init(); } ~D() { release(); }
??????? private: void init(); void release(); C c;
??? };
?
???
上面的定義中
D
類的
ctor
構(gòu)造過(guò)程如下:
??? A::A();
??? B::B();
??? c.C::C();
??? D::init();
?
??? D
類的
dtor
析構(gòu)過(guò)程如下:
??? D::release();
??? c.C::~C();
??? B::~B();
??? A::~A();
?
???
更復(fù)雜的繼承關(guān)系以及多重繼承的構(gòu)造和析構(gòu)過(guò)程類似,有興趣的人可以寫(xiě)程序測(cè)試:)
?
???
還有一個(gè)問(wèn)題,編譯器會(huì)在什么時(shí)候自動(dòng)產(chǎn)生
ctor
和
dtor
的呢,又是如何產(chǎn)生的呢
???
其實(shí)很簡(jiǎn)單,當(dāng)你沒(méi)有寫(xiě)缺省構(gòu)造函數(shù)(
default constructor
)和缺省析構(gòu)函數(shù)(
default destructor
)的時(shí)候,編譯器就會(huì)給你自動(dòng)生成一個(gè),換句話說(shuō),任何類都有構(gòu)造函數(shù)和析構(gòu)函數(shù),雖然有時(shí)候什么都不做,還有復(fù)制構(gòu)造函數(shù)(
copy ctor
)也會(huì)自動(dòng)生成。但是如何產(chǎn)生會(huì)跟你的類的成員有關(guān)。如果成員都是原生類型,還有如果類成員也全部為原生類型,
ctor
將只會(huì)跟普通變量定義的初始化一樣,給一個(gè)初值,
dtor
則什么都不做,
copy ctor
則會(huì)使用內(nèi)存復(fù)制(
memcpy
)的方式復(fù)制對(duì)象。如果成員包含一個(gè)或多個(gè)類成員,而且至少有一個(gè)類成員定義有缺省構(gòu)造方法,則產(chǎn)生的
ctor
會(huì)依次調(diào)用每個(gè)成員的
ctor
。
dtor
和
copy-ctor
產(chǎn)生方法類似。(詳見(jiàn)《
Inside the C++ Object Model
》)
?
???
多態(tài)(
Polymorphism
)和虛函數(shù)(
Virtual function
)
?
???
多態(tài)是面向?qū)ο蟮幕咎匦裕?/span>
C++
里是通過(guò)
virtual
關(guān)鍵詞來(lái)提供的,它是通過(guò)在類對(duì)象里加入
vtbl
虛函數(shù)表來(lái)實(shí)現(xiàn)的,這一點(diǎn)相信大部分程序員都很清楚,不過(guò)怎么做到多態(tài)功能估計(jì)了解的不多了。要詳細(xì)了解,還請(qǐng)閱讀《
Inside the C++ Object Model
》一書(shū),下面簡(jiǎn)單介紹一下原理。
?
???
一般編譯都會(huì)給包含有
virtual function
的類頭部(有的編譯也會(huì)放到底部,比如
VC
)增加一個(gè)成員
vptr
指針,指向一個(gè)
vtbl
虛函數(shù)表,為定長(zhǎng)數(shù)組,大小是所有帶
virtual
的函數(shù)數(shù)目再加
1
。虛函數(shù)指針從
vtbl[1]
開(kāi)始,按照定義順序,指向特定的函數(shù)實(shí)現(xiàn)。如果子類定義了父類中帶
virtual
的函數(shù),則
vtbl
相應(yīng)的指針指向子類的函數(shù)實(shí)現(xiàn),否則就指向父類的實(shí)現(xiàn)。另外再說(shuō)明一點(diǎn),其中
vtbl[0]
是有別的用途,用來(lái)存放類型信息,做
dynamic_cast
用途。
???
仍以上面的例子為例,如下的代碼編譯器是如何處理:
?
??? A *p = new D();???? // up-cast
??? p->vfunc1();??????? ?//
編譯器會(huì)轉(zhuǎn)化為如下代碼
(*(p->vptr))[n](p); // n
為編譯期確定的固定數(shù),即相應(yīng)
virtual function
//
所在位置
?
???
需要牢記一點(diǎn),總是讓
base class
擁有
virtual destructor
。因?yàn)楫?dāng)如下操作時(shí)
?
?
??delete p;
?
???
如果
A
和
B
的析構(gòu)函數(shù)不是虛函數(shù),則不會(huì)調(diào)用子類
D
的
dtor
,就有可能造成內(nèi)存泄露或者資源沒(méi)有釋放等嚴(yán)重問(wèn)題。如果給
base class
加了
virtual dtor
,由于有多態(tài)的特性,就會(huì)自動(dòng)調(diào)用
subclass
的
dtor
,接下來(lái)就會(huì)上面的介紹,依次調(diào)用各個(gè)
base class
的
dtor
,因而就沒(méi)有問(wèn)題了。
?
???
C++ template
和
STL containers
?
???
C++ template
即模板技術(shù)是實(shí)現(xiàn)泛型編程技術(shù)的,能夠使得寫(xiě)一份代碼可以應(yīng)用到類似用途的不同地方。模板技術(shù)其實(shí)原理比較簡(jiǎn)單,但是使用還是比較復(fù)雜的,看看
STL
源碼就知道了,如果還不相信,再看看
Boost
代碼好了,會(huì)把你搞得暈頭轉(zhuǎn)向。候捷老師把這個(gè)技術(shù)講解得非常清楚易懂,還具體分析了
STL
里各個(gè)大組件的運(yùn)作原理,我這里就不講述了,基本都是源碼的剖析,請(qǐng)閱讀候捷老師的《
STL
源碼剖析》一書(shū)。
?
???
在講解
STL
中用模板如何實(shí)現(xiàn)
function class
(實(shí)現(xiàn)函數(shù)功能的類,在
stl_functions.h
)中,有這樣一段代碼
?
template <class _Operation>
class binder1st
? : public unary_function<typename _Operation::second_argument_type,
????????????????????????? typename _Operation::result_type> {
protected:
? _Operation op;
? typename _Operation::first_argument_type value;
public:
? binder1st(const _Operation& __x,
??????????? const typename _Operation::first_argument_type& __y)
????? : op(__x), value(__y) {}
? typename _Operation::result_type
? operator()(const typename _Operation::second_argument_type& __x) const {
??? return op(value, __x);
? }
};
?
???
有人提出上面
_Operation op;
為什么不定義為引用,如
_Operation &op;
呢。我的想法如下,因?yàn)闃?gòu)造方法為
? binder1st(const _Operation& __x, //
這里為
const
類型
??????????? const typename _Operation::first_argument_type& __y)
?
???
傳入的參數(shù)為
const
類型,這時(shí)不應(yīng)在本調(diào)用方法(這里是構(gòu)造方法)之外使用引用或指針指向它,因?yàn)閹?/span>
const T &t
的參數(shù)一般情況都視為臨時(shí)對(duì)象,很有可能是在方法調(diào)用的時(shí)候臨時(shí)產(chǎn)生的,比如說(shuō)自動(dòng)轉(zhuǎn)型產(chǎn)生的臨時(shí)對(duì)象都是
const T &
類型,它的生命周期都在此方法調(diào)用期間內(nèi),方法調(diào)用結(jié)束即被銷(xiāo)毀,所以就不能在方法外部用引用或指針之類指向它了。舉例來(lái)說(shuō),可能比較容易理解,比如大家常用的
string
類,假如有一個(gè)方法和調(diào)用如下:
?
??? void func(const string &s);
??? func("abcdfd");
?
???
這個(gè)時(shí)候就會(huì)出現(xiàn)自動(dòng)轉(zhuǎn)型行為,編譯器會(huì)做如下處理
?
??? func(string("abcdfd"));
?
???
即產(chǎn)生一個(gè)臨時(shí)的
string
對(duì)象,這個(gè)對(duì)象是以
const
類型傳入的。假如你的方法定義改成如下
?
??? void func(string &s);
?
???
現(xiàn)在大部分編譯器嚴(yán)格的處理都會(huì)報(bào)錯(cuò),以前的
VC6
就不會(huì),但是好像最新的
VC2005
也報(bào)錯(cuò)了。
???
這是其中一個(gè)原因,還有一個(gè)原因我認(rèn)為是
_Operation
類只是一個(gè)
function class
,沒(méi)有成員,所以做復(fù)制構(gòu)造也不會(huì)有多大的開(kāi)銷(xiāo),基本不會(huì)影響效率。再加模板和
inline
方法的處理,編譯器經(jīng)過(guò)優(yōu)化,應(yīng)該都不會(huì)產(chǎn)生臨時(shí)對(duì)象了,所以也不必用引用了。不過(guò)我覺(jué)得最重要是上面第一個(gè)原因。
?
???
內(nèi)存池和小對(duì)象分配器(
memory pool, small object allocator
)
?
???
候捷老師在內(nèi)存池方面也有很豐富的研究經(jīng)驗(yàn),他基本將目前主流的內(nèi)存池實(shí)作都剖析了一遍,介紹了它們各自的特點(diǎn),以及如何與上層框架的配合。內(nèi)存池是一個(gè)非?;A(chǔ)也非常關(guān)鍵的底層庫(kù),一般大型的框架自己都帶有一個(gè)內(nèi)存池庫(kù),比如
STL
、
MFC
等。即使在目前內(nèi)存比較便宜的今天,內(nèi)存資源也是最寶貴的系統(tǒng)資源之一,設(shè)計(jì)一個(gè)優(yōu)秀的內(nèi)存池對(duì)提高系統(tǒng)的效率和穩(wěn)定性都非常有幫助,尤其是設(shè)計(jì)專門(mén)針對(duì)小內(nèi)存對(duì)象(一般低于
128
字節(jié))的分配器非常重要,因?yàn)檫@樣對(duì)象分配和釋放非常頻繁,只用簡(jiǎn)單的
malloc()
和
free()
來(lái)處理非常影響效率,不是一個(gè)優(yōu)秀的設(shè)計(jì)。下面我簡(jiǎn)要介紹一下目前主流內(nèi)存池設(shè)計(jì)的特點(diǎn),以及我自己的想法,另外再加一個(gè)候捷老師沒(méi)提到
ACE
中的內(nèi)存池管理器的設(shè)計(jì)特點(diǎn)。
?
???
SGI STL
中的內(nèi)存分配器(
allocator
)
?
??? SGI STL
的
allocator
應(yīng)該是目前設(shè)計(jì)最優(yōu)秀的
C++
內(nèi)存分配器之一了,它的運(yùn)作原理候捷老師在《
STL
源碼剖析》里講解得非常清楚?;舅悸肥窃O(shè)計(jì)一個(gè)
free_list[16]
數(shù)組,負(fù)責(zé)管理從
8 bytes
到
128 bytes
不同大小的內(nèi)存塊(
chunk
),每一個(gè)內(nèi)存塊都由連續(xù)的固定大?。?/span>
fixed size block
)的很多
chunk
組成,并用指針鏈表串接起來(lái)。比如說(shuō)
?
??? free_list[3]->start_notuse->next_notuse->next_notuse->...->end_notuse;
?
???
當(dāng)用戶要獲取此大小的內(nèi)存時(shí),就在
free_list
的鏈表找一個(gè)最近的
free chunk
回傳給用戶,同時(shí)將此
chunk
從
free_list
里刪除,即把此
chunk
前后
chunk
指針鏈結(jié)起來(lái)。用戶使用完釋放的時(shí)候,則把此
chunk
放回到
free_list
中,應(yīng)該是放到最前面的
start_free
的位置。這樣經(jīng)過(guò)若干次
allocator
和
deallocator
后,
free_list
中的鏈表可能并不像初始的時(shí)候那么是
chunk
按內(nèi)存分布位置依次鏈接的。假如
free_list
中不夠時(shí),
allocator
會(huì)自動(dòng)再分配一塊新的較大的內(nèi)存區(qū)塊來(lái)加入到
free_list
鏈表中。
???
可以自動(dòng)管理多種不同大小內(nèi)存塊并可以自動(dòng)增長(zhǎng)的內(nèi)存池,這是
SGI STL
分配器設(shè)計(jì)的特點(diǎn)。
?
???
Loki
中的小對(duì)象分配器(
small object allocator
)
?
??? Loki
的分配器與
SGI STL
的原理類似,不同之處是它管理
free_list
不是固定大小的數(shù)組,而是用一個(gè)
vector
來(lái)實(shí)現(xiàn),因此可以用戶指定
fixed size block
的大小,不像
SGI STL
是固定最大
128 bytes
的。另外它管理
free chunks
的方式也不太一樣,
Loki
是由一列記錄了
free block
位置等信息的
Chunk
類的鏈表來(lái)維護(hù)的,
free blocks
則是分布在另外一個(gè)連續(xù)的大內(nèi)存區(qū)間中。而且
free Chunks
也可以根據(jù)使用情況自動(dòng)增長(zhǎng)和減少合適的數(shù)目,避免內(nèi)存分配得過(guò)多或者過(guò)少。
??? Loki
的分配器使用也不太一樣,可以直接調(diào)用,如下
?
??? SmallObjAllocator myAlloc(2048, 256); //
參數(shù)
1
為
chunk size
????????????????????????????????????????? //
參數(shù)
2
為
max fixed size block size
??? //
可以用于小于
256 bytes
的各種大小內(nèi)存的分配
??? void *p1 = (void*)myAlloc.Allocate(20);
??? void *p2 = (void*)myAlloc.Allocate(100);
??? void *p3 = (void*)myAlloc.Allocate(256);
??? void *p4 = (void*)myAlloc.Allocate(300); //
大于
256
將轉(zhuǎn)交給系統(tǒng)處理
??? myAlloc.Deallocate(p1,20);
??? myAlloc.Deallocate(p2,100);
??? myAlloc.Deallocate(p3,256);
??? myAlloc.Deallocate(p4,300);
?
???
MFC
的
CPlex
和
CPtrList
(扮演
memory pool
角色)
?
???
CPlex
任務(wù)比較簡(jiǎn)單,只負(fù)責(zé)管理一大塊
memory
并串接起來(lái),用戶每次獲取都返回一大塊。分割由使用者(如
Collection classes
,
CFixedAlloc
)將這一大塊切割為一個(gè)個(gè)小的內(nèi)存塊。
???
CPtrList
則負(fù)責(zé)管理這些切割后的小內(nèi)存塊,這一點(diǎn)有點(diǎn)類似
Loki
中的
free Chunks
,不過(guò)要簡(jiǎn)單多了。
??? MFC
還有一個(gè)類叫
CFixedAlloc
,它是提供給應(yīng)用類來(lái)分配固定大小(根據(jù)具體應(yīng)用類的大?。┑膬?nèi)存分配器。通過(guò)在應(yīng)用類中定義
DECLARE_FIXED_ALLOC(Foo)
和
IMPLEMENT_FIXED_ALLOC(Foo)
兩個(gè)宏來(lái)實(shí)現(xiàn)。
?
???
Boost
的
object_pool
?
???
Boost
中的
object_pool
也是一個(gè)可以根據(jù)用戶具體應(yīng)用類的大小來(lái)分配內(nèi)存塊的,也是通過(guò)維護(hù)一個(gè)
free nodes
的鏈表來(lái)管理的??梢宰詣?dòng)增加
nodes
塊,初始是
32
個(gè)
nodes
,每次增加都以兩倍數(shù)向
system heap
要內(nèi)存塊。
object_pool
管理的內(nèi)存塊需要在其對(duì)象銷(xiāo)毀的時(shí)候才返還給
system heap
。
?
???
ACE
中的
ACE_Cached_Allocator
和
ACE_Free_List
?
??? ACE
框架中也有一個(gè)可以維護(hù)固定大小的內(nèi)存塊的分配器,原理與上面講的內(nèi)存池都差不多。它是通過(guò)在
ACE_Cached_Allocator
中定義個(gè)
Free_list
鏈表來(lái)管理一個(gè)連續(xù)的大內(nèi)存塊的,里面包含很多小的固定大小的未使用的區(qū)塊(
free chunk
),同時(shí)還使用
ACE_unbounded_Set
維護(hù)一個(gè)已使用的
chuncks
,管理方式與上面講的內(nèi)存池類似。也可以指定
chunks
的數(shù)目,也可以自動(dòng)增長(zhǎng),定義大致如下所示:
?
template<class T>
class ACE_Cached_Allocator : public ACE_New_Allocator<T> {
public:
??? // Create a cached memory pool with @a n_chunks chunks
??? // each with sizeof (TYPE) size.
??? ACE_Cached_Allocator(SIZET n_chunks = ACE_DEFAULT_INIT_CHUNKS);
??? T* allocate();
??? void deallocate(T* p);
private:
??? // List of memory that we have allocated.
??? Fast_Unbounded_Set<char *> _allocated_chunks;
??? // Maintain a cached memory free list.
??? ACE_Cached_Free_List<ACE_Cached_Mem_Pool_Node<T> > _free_list;
};
?
???
設(shè)計(jì)模式
?
???
最后一個(gè)主題重點(diǎn)講講設(shè)計(jì)模式,設(shè)計(jì)模式現(xiàn)在已經(jīng)應(yīng)用很廣泛了,可以說(shuō)是無(wú)處不在。設(shè)計(jì)模式現(xiàn)在對(duì)程序員是非常的重要,甚至到了不懂設(shè)計(jì)模式就不算真正的程序員一樣。不過(guò)設(shè)計(jì)模式卻又是非常高階的理論,需要有多年的編程經(jīng)驗(yàn)才能真正領(lǐng)悟,所以學(xué)習(xí)起來(lái)非常頭痛。因?yàn)樗览矸浅:?jiǎn)單,但是卻非常抽象,候捷老師通過(guò)一大堆實(shí)際案例給我們逐個(gè)講述了幾個(gè)常用的模式的區(qū)別和用法。設(shè)計(jì)模式最經(jīng)典最權(quán)威當(dāng)屬著名的有字天書(shū)
GoF
的《
Design Patterns
》了,我結(jié)合自己學(xué)習(xí)和實(shí)踐的體會(huì)介紹一下幾個(gè)模式。
?
???
結(jié)構(gòu)型模式之
Composite
(合成模式)
?
??? GoF
的定義:
Compose objects into tree structures to represent part-whole hierarchies. Composite lets clients treat individual objects and compositions of objects.
翻譯為中文大致意思是:將對(duì)象
(s)
組成為樹(shù)狀結(jié)構(gòu),用以表示“局部
-
整體”的層次體系,使得讓
clients
可以以一致的方式對(duì)待“單個(gè)對(duì)象”和“合成對(duì)象”。
?
???
比較典型的例子就是文件系統(tǒng)中“文件”和“目錄”的關(guān)系,還有
Windows
窗口系統(tǒng)也是,在一個(gè)窗口中還可以開(kāi)另一個(gè)窗口,多個(gè)窗口組合成的窗口還可以當(dāng)作一個(gè)窗口放入另一個(gè)窗口中,比如在
Word
中打開(kāi)多個(gè)文檔就是這種情況。
Composite
模式的好處就是使得
clients
調(diào)用簡(jiǎn)單,可以用一致的接口處理單個(gè)對(duì)象或者多個(gè)單一對(duì)象組合成的對(duì)象。
?
???
實(shí)例:
Java swing library
中
Component
,
Label
,
Container
就是
Composite
模式的應(yīng)用。其中
Label
和
Container
都繼承自
Component
,但是
C
ontainer
中只是一個(gè)存放
Component
的數(shù)組,所以
Container
中就可以放很多
Component
,比如
ScrollPane
就是繼承自
Container
,它可以放
Label
,還有
List
,
Scrollbar
等等,甚至還可以放一個(gè)
ScrollPane
,所以就達(dá)到了
Composite
模式的效果,簡(jiǎn)化了
client
的使用。
?
???
結(jié)構(gòu)型模式之
Decorator
(裝飾模式)
?
??? GoF
的定義:
Attach additional responsibilities to an object dynamically. Decorators provide a flexible alternative to subclassing for extending functionality.
翻譯為中文大致的意思是:以動(dòng)態(tài)的方式給一個(gè)對(duì)象添加一些額外的職責(zé),使得不必進(jìn)行
subclassing
就能擴(kuò)展功能。
?
???
Decorator
模式與
Composite
模式的區(qū)別就是它只內(nèi)含一個(gè)
component object field
,而
Composite
則內(nèi)含一個(gè)
collection of component field
。
Decorator
負(fù)責(zé)將一個(gè)對(duì)象“裝飾”起來(lái),做一些“改造或者擴(kuò)展”,提供額外的功能,它只針對(duì)一個(gè)
class
。而
Composite
是一組“類似”的對(duì)象及其容納它們的容器一視同仁,使得
client
更簡(jiǎn)單地處理單個(gè)對(duì)象和一組對(duì)象。它們目的不一樣。
?
實(shí)例:
Java IO library
中
BufferedReader
,
Reader
之間使用的就是
Decorator
模式,其中
BufferedReader
繼承自
Reader
,同時(shí)它內(nèi)部含有一個(gè)
Reader
引用,它是通過(guò)另一個(gè)
Reader
對(duì)象構(gòu)造而來(lái),因此就為
Reader
提供更多的功能,如帶緩沖的
Reader
。使用非常簡(jiǎn)單,只需要如此定義:
?
Reader in = new BufferedReader(new FileReader("test.txt"));
?
就為文件讀取增加了帶緩沖的
IO
功能,非常方便。還可以多個(gè)
Decorator
的類組合使用,可以提供更強(qiáng)大的功能,多使用一下
Java IO library
就會(huì)體會(huì)到。
?
???
行為模式之
Observer
(觀察者模式)
?
??? GoF
的定義:
Define a one-to-many dependency between objects so that when one object changes state, all its dependents are notified and updated automatically.
翻譯為中文大致意思是:在
objects
之間定義一個(gè)“一對(duì)多”的依賴關(guān)系,使得當(dāng)這個(gè)
object
改變狀態(tài)時(shí),所有依賴它的
objects
都能獲得通知并自動(dòng)更新。
?
???
Observer
是用于做“通知”用途的,就像“
publish-subscribe
”,它能夠做到注冊(cè)需要通知的對(duì)象,并自動(dòng)通知它們來(lái)更新,它們都是被動(dòng)地被通知,而不是主動(dòng)觀察。
?
???
實(shí)例:
MFC
里
CView
和
CDocument
之間就是一個(gè)觀察者模式,
CView
是
Observer
即觀察者,
CDocument
是
Observable
即被觀察者,當(dāng)
CDocument
修改后會(huì)自動(dòng)通知所有的
CView
對(duì)象來(lái)自動(dòng)更新它們顯示的內(nèi)容,這一點(diǎn)可以用
Word
很容易試出來(lái)。還有最新的
Windows
圖形界面框架
WinForm
中的窗口之間消息傳遞等用的也是
Observer
模式,一個(gè)窗口發(fā)生改變時(shí)會(huì)自動(dòng)通知所有與它有關(guān)系的窗口,來(lái)自動(dòng)更新信息等,這一點(diǎn)
Jeffrey Richter
可以作證
J
?
???
行為模式之
Template Method
(模板方法)
?
??? GoF
的定義:
Define the skeleton of an algorithm in an operation, deferring somesteps to subclasses. Template Method lets subclasses redefine certain steps ofan algorithm without changing the algorithm's structure.
翻譯為中文大致意思是:定義一個(gè)算法的骨干,延緩其中某些步驟以便在
subclasses
中定義它們。
Template Method
使得
subclasses
在不改變算法的體系結(jié)構(gòu)的前提下得到重新定義算法內(nèi)的某些步驟。
?
???
Template Method
其實(shí)最常用了,在
C++
里的應(yīng)用就是使用
virtual function
實(shí)現(xiàn)的,給一個(gè)
base class
定義一個(gè)
virtual
方法,但是不實(shí)現(xiàn),而是在它的
subclasses
中實(shí)現(xiàn)它,這就是
Template Method
。這個(gè)模式的好處顯而易見(jiàn),就是在你設(shè)計(jì)
base class
的時(shí)候并不知道這個(gè)方法具體如何實(shí)現(xiàn),但是卻需要在
base class
里某個(gè)地方需要調(diào)用它以完成一個(gè)完整的算法,這時(shí)候就是需要用這個(gè)模式了。
?
???
實(shí)例:例子太多了,到處都是。
?
???
行為模式之
Strategy
(策略模式)
?
??? GoF
的定義:
Define a family of algorithms, encapsulate each one, and make theminterchangeable. Strategy lets the algorithm vary independently from client that use it.
翻譯為中文大致意思是:定義一族算法,把每個(gè)算法封裝起來(lái),并使它們可以相互替換。
Stategy
使得算法給
client
使用的時(shí)候變得完全獨(dú)立。
?
???
Strategy
模式完全符合
OCP
(
Open-Closed Principle
,開(kāi)閉原則),可以在不需要做
subclass
更不需要修改
base class
的情況下在
Runtime
的時(shí)候擴(kuò)展系統(tǒng)的功能,它不像
Template Method
需要
subclass
才能擴(kuò)展新的功能。
?
???
實(shí)例:
Strategy
典型的應(yīng)用是在薪資系統(tǒng)中,當(dāng)定義
Employee
類的時(shí)候,如果使用
Template Method
模式,就無(wú)法更改員工領(lǐng)薪資的方式等,比如員工晉升的時(shí)候。這時(shí)候就需要用
Strategy
模式,在
Employee
中定義一個(gè)指針指向具體的
PaymentMethod
對(duì)象,如果需要更改領(lǐng)薪資的方式的時(shí)候,只需要將它指向新的
PaymentMehtod
實(shí)現(xiàn)就可以了。
?
???
結(jié)構(gòu)模式之
Adapter
(適配器模式)
?
??? GoF
的定義:
Convert the interface of a class into another interface clients expect.Adapter lets classes work together that couldn't otherwise because of incompatible interfaces.
翻譯為中文大致意思是:將一個(gè)
class
的接口轉(zhuǎn)換為另外一種
clients
所期望的接口,使得這些
classes
可以更好地協(xié)同工作起來(lái),而不至于因?yàn)閯e的不兼容的問(wèn)題所影響。
?
???
Adapter
模式屬于結(jié)構(gòu)型模式,需要有
Adaptee
(被適配者)和
Adaptor
(適配器)兩個(gè)對(duì)象。一般情況下,我們通過(guò)繼承(
Inheritance
)或者合成(
Composition
)的方式擴(kuò)展類的功能后,會(huì)產(chǎn)生很多接口形式不同的類,但是我們想用同樣的方式調(diào)用它們,又不想改造它們(否則違反
OCP
原則),怎么辦呢?這種情況下就需要用到
Adapter
模式了,這些被“改造”的類就叫做
Adaptee
,而我們就需要寫(xiě)個(gè)
Adaptor
類來(lái)轉(zhuǎn)調(diào)它們,把調(diào)用的接口改成統(tǒng)一的形式。
?
???
實(shí)例:
Java IO library
里的
InputStreamReader
就是一個(gè)
Adapter
,它負(fù)責(zé)將
InputStream
對(duì)象轉(zhuǎn)換為
Reader
對(duì)象的接口形式,使得用戶可以像使用其它
Reader
一樣的方式來(lái)使用
InputStream
對(duì)象,比如:
?
InputStreamReader isr = new InputStreamReader(
new FileInputStream("test.txt"));
??? BufferedReader br = new BufferedReader(isr);
??? String line = br.readLine();
?
???
這就做到了在不改造原來(lái)設(shè)計(jì)的類的接口的情況,擴(kuò)展了類的應(yīng)用范圍。一般情況下,
Adapter
模式需要結(jié)合
Factory
模式和
Proxy
模式來(lái)使用,使得接口的訪問(wèn)更加一致性,更容易改造系統(tǒng)。
?
???
結(jié)構(gòu)模式之
Facade
(外觀模式)
?
??? GoF
的定義:
Provide a unified interface to a set of interfaces in a subsystem.Facade defines a higher-level interface that makes the subsystem easier to use.
翻譯為中文大致意思是:為一個(gè)子系統(tǒng)的一批接口提供一個(gè)統(tǒng)一標(biāo)準(zhǔn)的接口,
Facade
定義更高層次的接口,使得子系統(tǒng)更容易使用。
?
???
Facade
模式我感覺(jué)是一個(gè)更高層次的
Adapter
,它是用來(lái)理順系統(tǒng)之間的關(guān)系,降低系統(tǒng)間的耦合度的常用方法,其實(shí)我們可能在設(shè)計(jì)系統(tǒng)的時(shí)候就在不知不覺(jué)地應(yīng)用這個(gè)模式了。大部分將業(yè)務(wù)邏輯層和表示層分離的框架都是應(yīng)用
Facade
模式實(shí)現(xiàn)的,使得上層使用者完全不必理會(huì)底層的實(shí)現(xiàn),卻又有統(tǒng)一的使用界面。
?
???
實(shí)例:
J2EE EJB
中
Session Bean
就是一種典型的
Facade
模式,即
Session Facade
。它完全將業(yè)務(wù)對(duì)象封裝起來(lái),
EJB
客戶端訪問(wèn)
Session Bean
來(lái)代替訪問(wèn)業(yè)務(wù)對(duì)象。這就大大簡(jiǎn)化了
EJB
的系統(tǒng)結(jié)構(gòu),
Session Bean
就是相當(dāng)于一個(gè)外觀,也相當(dāng)于一個(gè)總管,把業(yè)務(wù)對(duì)象都管理起來(lái),不讓客戶端直接“接觸”到它們。
?
???
《設(shè)計(jì)模式》一書(shū)中還有很多模式這里就不一一介紹了。其實(shí)我們大家都可以自己設(shè)計(jì)一種模式,但是肯定沒(méi)有
GoF
的那
23
個(gè)著名了。不過(guò)現(xiàn)在還有很多別的也很有名的模式,比如說(shuō)
Wrapper
模式(相當(dāng)于
Decorator
)、
DAO
模式(
Data Access Object
模式,
OR
映射系統(tǒng)里常用到)、
MVC
模式(著名的
Model-View-Control
架構(gòu)模式)等等。我的一個(gè)觀點(diǎn),只有大量地實(shí)踐,持續(xù)地學(xué)習(xí),就會(huì)不斷提升自己設(shè)計(jì)模式的層次,如果想靠一兩年的學(xué)習(xí)就想精通它,是不可能的任務(wù)
J
?
???
寫(xiě)了這么多,終于寫(xiě)完了,呵呵。寫(xiě)完后,有兩點(diǎn)體會(huì):
1,
寫(xiě)源碼剖析類的文章,就像是給代碼做翻譯,把代碼翻譯成圖片或文字。
2
,寫(xiě)
OO/DP
之類理論的文章,則相當(dāng)于創(chuàng)造,不同的人有不同的體會(huì),技術(shù)學(xué)習(xí)層面不同的理解也不同。還是那句話:理論需要從實(shí)踐中來(lái),多學(xué)習(xí)多實(shí)踐。歡迎指正!
?
最后我列幾本候捷老師推薦的書(shū):
??? 1
,
C++
程序員必備的書(shū)(我以為,即時(shí)你不看,也建議備上,充門(mén)面也好,爭(zhēng)吵也好都很用)
?????? a)
《
C++ Programming Language
》
Bjarne Stroustrup
?????? b)
《
C++ Primer
》
Stanley B Lippman
?????? c)
《
Effective C++
》
Scott Meyers
?????? d)
《
Design Patterns
》
GoF
??? 2,
提高或?qū)W習(xí)的書(shū)
?????? a)
《
Inside The C++ Object Model
》
Stanley B Lippman
?????? b)
《
Thinking in C++
》
Bruce Eckel
?????? c)
《
More Effective C++
》
Scott Meyers
?????? d)
《
STL
源碼剖析》侯捷
?????? e)
《
Modern C++ Design
》
Andrei Alexandrescu
?????? f)
《
Small Memory Software - Patterns for memory management
》
?
?
?
--
適合讀者:
C++
中高級(jí)程序員
--
作者:
naven
博客:
http://m.shnenglu.com/javenstudio/
2006年12月28日