侯捷《C++/OOP/GP/DP》講座心得
?????????????????
??????????????????????????????????????????????????????????????????????????????????????????????
———
作者:
naven
?
???
很高興侯捷老師又來公司了,給我們上了四天非常生動(dòng)的技術(shù)講座,受益匪淺,現(xiàn)在我簡要介紹一下我的學(xué)習(xí)心得,與大家分享。這次講座主要集中在《
C++/OOP/GP/DP
》主題,針對(duì)有一些編程基礎(chǔ)的工程師,對(duì)一些常用的代碼和設(shè)計(jì)做了非常通俗易懂的剖析,非常有幫助。當(dāng)然更深入的理解還需要結(jié)合多種技術(shù)名著來學(xué)習(xí),我結(jié)合我的理解以及自己的學(xué)習(xí)和開發(fā)的經(jīng)驗(yàn)介紹一下
C++/OO/Template
以及
Design Pattern
的理會(huì),考慮到講座的性質(zhì),我并不直述本次講座的內(nèi)容,歡迎批評(píng)指正
J
?
???
侯捷老師的講座基本是講述他多年來在
C++
領(lǐng)域的研究成果,基本大部分都可以在他的書籍和網(wǎng)站上能讀到,但是考慮到最近幾年軟件技術(shù)的蓬勃發(fā)展,如
Design Pattern
的更廣泛應(yīng)用,又有許多心得,基本上是較為泛的基礎(chǔ)的層面,并結(jié)合實(shí)際代碼和應(yīng)用,對(duì)實(shí)際項(xiàng)目開發(fā)非常有益。下面我逐個(gè)主題泛泛地講一遍。
?
???
面向?qū)ο笾械暮铣桑?/span>
Composition
)和繼承(
Inheritance
)關(guān)系
?
???
通常擴(kuò)展一個(gè)類的功能主要有兩種方式,一種是大家很熟悉的繼承(
inheritance
),另一種就是合成(
composition
),很多初學(xué)
OO
(面向?qū)ο螅┎⒂幸恍┙?jīng)驗(yàn)都很容易搞混這個(gè)的區(qū)別,其實(shí)很簡單,繼承是解決
Is-a
的問題,而合成是解決
Has-a
的問題。比如說小鳥有兩個(gè)翅膀,就是合成,而鳥是一種飛禽,就是繼承了,設(shè)計(jì)一個(gè)“小鳥”的類,它繼承自”飛禽”,就具有“飛”的特性,但要用合成的方法“包含”一個(gè)“翅膀”的類才具有真正“飛”的功能。
???
別看這兩個(gè)定義很簡單,其實(shí)很多人都犯過錯(cuò)誤,包括
Java
類庫的設(shè)計(jì)者,他們就把
Properties
直接“繼承”自
Hashtable
了,這里其實(shí)應(yīng)該用“合成”。
?
???
講到合成,就應(yīng)該說說聚合(
Aggregation
),它是描述整體和局部的關(guān)系,合成其實(shí)是一種“強(qiáng)烈”的聚合,它與局部具有相同的生命周期,“容納”局部的“對(duì)象”,而聚合只是“容納”局部的一個(gè)“指針”。比如說,人和腦袋就是合成,而汽車與發(fā)動(dòng)機(jī)就是聚合,改裝汽車可以任意替換更好的發(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
)來實(shí)現(xiàn)類似。但是在繼承里,一個(gè)被繼承的操作(
inherited operation
)通過
this
成員變量能夠經(jīng)常引用到那個(gè)接收的對(duì)象。為了在委托里達(dá)到同樣的效果,接受者傳遞它自己給它的委托者,以便被委托的操作能夠引用到這個(gè)接收者。
?
???
再說一下繼承(
Inheritance
),它是將基類(
base-class
)所有一切(包括
private
)都繼承下來,所以假如你想實(shí)現(xiàn)一個(gè)新的類,只想繼承一部分,就用合成(
Composition
)別用繼承。或者更進(jìn)一步來講,如果你想改造一個(gè)類,想改造一些接口(
interface
),也建議用合成,通過轉(zhuǎn)調(diào)內(nèi)部對(duì)象的方法實(shí)現(xiàn),別用虛函數(shù)(
virtual function
)。這是非常符合最基本的
OCP
設(shè)計(jì)原則(
Open-Closed Principle
,開閉原則)的方式了。
?
???
類的構(gòu)造(
Constructor
)和析構(gòu)(
Destructor
)
?
???
類的構(gòu)造和析構(gòu)是最基礎(chǔ)的知識(shí)了,任何一個(gè)類的對(duì)象產(chǎn)生和銷毀都必須有這兩個(gè)步驟,但是它們是如何工作的,編譯器是如何制造缺省的
ctor
和
dtor
的,估計(jì)少有人關(guān)注了。
???
一個(gè)類的對(duì)象的產(chǎn)生,會(huì)依次從它最里面的類開始構(gòu)造,同一個(gè)類會(huì)跟據(jù)內(nèi)部類成員定義的順序依次構(gòu)造。類對(duì)象的銷毀的過程則相反。基類的構(gòu)造器會(huì)在用戶定義的
ctor
之前調(diào)用,基類的析構(gòu)則是在用戶定義的
dtor
之后進(jìn)行。熟悉這些過程,非常有利于設(shè)計(jì)出優(yōu)秀的類庫,也不容易出現(xiàn)內(nèi)存泄露和資源耗盡等問題。下面舉個(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)造過程如下:
??? A::A();
??? B::B();
??? c.C::C();
??? D::init();
?
??? D
類的
dtor
析構(gòu)過程如下:
??? D::release();
??? c.C::~C();
??? B::~B();
??? A::~A();
?
???
更復(fù)雜的繼承關(guān)系以及多重繼承的構(gòu)造和析構(gòu)過程類似,有興趣的人可以寫程序測試:)
?
???
還有一個(gè)問題,編譯器會(huì)在什么時(shí)候自動(dòng)產(chǎn)生
ctor
和
dtor
的呢,又是如何產(chǎn)生的呢
???
其實(shí)很簡單,當(dāng)你沒有寫缺省構(gòu)造函數(shù)(
default constructor
)和缺省析構(gòu)函數(shù)(
default destructor
)的時(shí)候,編譯器就會(huì)給你自動(dòng)生成一個(gè),換句話說,任何類都有構(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)生方法類似。(詳見《
Inside the C++ Object Model
》)
?
???
多態(tài)(
Polymorphism
)和虛函數(shù)(
Virtual function
)
?
???
多態(tài)是面向?qū)ο蟮幕咎匦裕?/span>
C++
里是通過
virtual
關(guān)鍵詞來提供的,它是通過在類對(duì)象里加入
vtbl
虛函數(shù)表來實(shí)現(xiàn)的,這一點(diǎn)相信大部分程序員都很清楚,不過怎么做到多態(tài)功能估計(jì)了解的不多了。要詳細(xì)了解,還請(qǐng)閱讀《
Inside the C++ Object Model
》一書,下面簡單介紹一下原理。
?
???
一般編譯都會(huì)給包含有
virtual function
的類頭部(有的編譯也會(huì)放到底部,比如
VC
)增加一個(gè)成員
vptr
指針,指向一個(gè)
vtbl
虛函數(shù)表,為定長數(shù)組,大小是所有帶
virtual
的函數(shù)數(shù)目再加
1
。虛函數(shù)指針從
vtbl[1]
開始,按照定義順序,指向特定的函數(shù)實(shí)現(xiàn)。如果子類定義了父類中帶
virtual
的函數(shù),則
vtbl
相應(yīng)的指針指向子類的函數(shù)實(shí)現(xiàn),否則就指向父類的實(shí)現(xiàn)。另外再說明一點(diǎn),其中
vtbl[0]
是有別的用途,用來存放類型信息,做
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)存泄露或者資源沒有釋放等嚴(yán)重問題。如果給
base class
加了
virtual dtor
,由于有多態(tài)的特性,就會(huì)自動(dòng)調(diào)用
subclass
的
dtor
,接下來就會(huì)上面的介紹,依次調(diào)用各個(gè)
base class
的
dtor
,因而就沒有問題了。
?
???
C++ template
和
STL containers
?
???
C++ template
即模板技術(shù)是實(shí)現(xiàn)泛型編程技術(shù)的,能夠使得寫一份代碼可以應(yīng)用到類似用途的不同地方。模板技術(shù)其實(shí)原理比較簡單,但是使用還是比較復(fù)雜的,看看
STL
源碼就知道了,如果還不相信,再看看
Boost
代碼好了,會(huì)把你搞得暈頭轉(zhuǎn)向。候捷老師把這個(gè)技術(shù)講解得非常清楚易懂,還具體分析了
STL
里各個(gè)大組件的運(yùn)作原理,我這里就不講述了,基本都是源碼的剖析,請(qǐng)閱讀候捷老師的《
STL
源碼剖析》一書。
?
???
在講解
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)生的,比如說自動(dòng)轉(zhuǎn)型產(chǎn)生的臨時(shí)對(duì)象都是
const T &
類型,它的生命周期都在此方法調(diào)用期間內(nèi),方法調(diào)用結(jié)束即被銷毀,所以就不能在方法外部用引用或指針之類指向它了。舉例來說,可能比較容易理解,比如大家常用的
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
,沒有成員,所以做復(fù)制構(gòu)造也不會(huì)有多大的開銷,基本不會(huì)影響效率。再加模板和
inline
方法的處理,編譯器經(jīng)過優(yōu)化,應(yīng)該都不會(huì)產(chǎn)生臨時(shí)對(duì)象了,所以也不必用引用了。不過我覺得最重要是上面第一個(gè)原因。
?
???
內(nèi)存池和小對(duì)象分配器(
memory pool, small object allocator
)
?
???
候捷老師在內(nèi)存池方面也有很豐富的研究經(jīng)驗(yàn),他基本將目前主流的內(nèi)存池實(shí)作都剖析了一遍,介紹了它們各自的特點(diǎn),以及如何與上層框架的配合。內(nèi)存池是一個(gè)非常基礎(chǔ)也非常關(guān)鍵的底層庫,一般大型的框架自己都帶有一個(gè)內(nèi)存池庫,比如
STL
、
MFC
等。即使在目前內(nèi)存比較便宜的今天,內(nèi)存資源也是最寶貴的系統(tǒng)資源之一,設(shè)計(jì)一個(gè)優(yōu)秀的內(nèi)存池對(duì)提高系統(tǒng)的效率和穩(wěn)定性都非常有幫助,尤其是設(shè)計(jì)專門針對(duì)小內(nèi)存對(duì)象(一般低于
128
字節(jié))的分配器非常重要,因?yàn)檫@樣對(duì)象分配和釋放非常頻繁,只用簡單的
malloc()
和
free()
來處理非常影響效率,不是一個(gè)優(yōu)秀的設(shè)計(jì)。下面我簡要介紹一下目前主流內(nèi)存池設(shè)計(jì)的特點(diǎn),以及我自己的想法,另外再加一個(gè)候捷老師沒提到
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
源碼剖析》里講解得非常清楚。基本思路是設(shè)計(jì)一個(gè)
free_list[16]
數(shù)組,負(fù)責(zé)管理從
8 bytes
到
128 bytes
不同大小的內(nèi)存塊(
chunk
),每一個(gè)內(nèi)存塊都由連續(xù)的固定大小(
fixed size block
)的很多
chunk
組成,并用指針鏈表串接起來。比如說
?
??? 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é)起來。用戶使用完釋放的時(shí)候,則把此
chunk
放回到
free_list
中,應(yīng)該是放到最前面的
start_free
的位置。這樣經(jīng)過若干次
allocator
和
deallocator
后,
free_list
中的鏈表可能并不像初始的時(shí)候那么是
chunk
按內(nèi)存分布位置依次鏈接的。假如
free_list
中不夠時(shí),
allocator
會(huì)自動(dòng)再分配一塊新的較大的內(nèi)存區(qū)塊來加入到
free_list
鏈表中。
???
可以自動(dòng)管理多種不同大小內(nèi)存塊并可以自動(dòng)增長的內(nèi)存池,這是
SGI STL
分配器設(shè)計(jì)的特點(diǎn)。
?
???
Loki
中的小對(duì)象分配器(
small object allocator
)
?
??? Loki
的分配器與
SGI STL
的原理類似,不同之處是它管理
free_list
不是固定大小的數(shù)組,而是用一個(gè)
vector
來實(shí)現(xiàn),因此可以用戶指定
fixed size block
的大小,不像
SGI STL
是固定最大
128 bytes
的。另外它管理
free chunks
的方式也不太一樣,
Loki
是由一列記錄了
free block
位置等信息的
Chunk
類的鏈表來維護(hù)的,
free blocks
則是分布在另外一個(gè)連續(xù)的大內(nèi)存區(qū)間中。而且
free Chunks
也可以根據(jù)使用情況自動(dòng)增長和減少合適的數(shù)目,避免內(nèi)存分配得過多或者過少。
??? 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ù)比較簡單,只負(fù)責(zé)管理一大塊
memory
并串接起來,用戶每次獲取都返回一大塊。分割由使用者(如
Collection classes
,
CFixedAlloc
)將這一大塊切割為一個(gè)個(gè)小的內(nèi)存塊。
???
CPtrList
則負(fù)責(zé)管理這些切割后的小內(nèi)存塊,這一點(diǎn)有點(diǎn)類似
Loki
中的
free Chunks
,不過要簡單多了。
??? MFC
還有一個(gè)類叫
CFixedAlloc
,它是提供給應(yīng)用類來分配固定大小(根據(jù)具體應(yīng)用類的大小)的內(nèi)存分配器。通過在應(yīng)用類中定義
DECLARE_FIXED_ALLOC(Foo)
和
IMPLEMENT_FIXED_ALLOC(Foo)
兩個(gè)宏來實(shí)現(xiàn)。
?
???
Boost
的
object_pool
?
???
Boost
中的
object_pool
也是一個(gè)可以根據(jù)用戶具體應(yīng)用類的大小來分配內(nèi)存塊的,也是通過維護(hù)一個(gè)
free nodes
的鏈表來管理的。可以自動(dòng)增加
nodes
塊,初始是
32
個(gè)
nodes
,每次增加都以兩倍數(shù)向
system heap
要內(nèi)存塊。
object_pool
管理的內(nèi)存塊需要在其對(duì)象銷毀的時(shí)候才返還給
system heap
。
?
???
ACE
中的
ACE_Cached_Allocator
和
ACE_Free_List
?
??? ACE
框架中也有一個(gè)可以維護(hù)固定大小的內(nèi)存塊的分配器,原理與上面講的內(nèi)存池都差不多。它是通過在
ACE_Cached_Allocator
中定義個(gè)
Free_list
鏈表來管理一個(gè)連續(xù)的大內(nèi)存塊的,里面包含很多小的固定大小的未使用的區(qū)塊(
free chunk
),同時(shí)還使用
ACE_unbounded_Set
維護(hù)一個(gè)已使用的
chuncks
,管理方式與上面講的內(nèi)存池類似。也可以指定
chunks
的數(shù)目,也可以自動(dò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)用很廣泛了,可以說是無處不在。設(shè)計(jì)模式現(xiàn)在對(duì)程序員是非常的重要,甚至到了不懂設(shè)計(jì)模式就不算真正的程序員一樣。不過設(shè)計(jì)模式卻又是非常高階的理論,需要有多年的編程經(jīng)驗(yàn)才能真正領(lǐng)悟,所以學(xué)習(xí)起來非常頭痛。因?yàn)樗览矸浅:唵危菂s非常抽象,候捷老師通過一大堆實(shí)際案例給我們逐個(gè)講述了幾個(gè)常用的模式的區(qū)別和用法。設(shè)計(jì)模式最經(jīng)典最權(quán)威當(dāng)屬著名的有字天書
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)
組成為樹狀結(jié)構(gòu),用以表示“局部
-
整體”的層次體系,使得讓
clients
可以以一致的方式對(duì)待“單個(gè)對(duì)象”和“合成對(duì)象”。
?
???
比較典型的例子就是文件系統(tǒng)中“文件”和“目錄”的關(guān)系,還有
Windows
窗口系統(tǒng)也是,在一個(gè)窗口中還可以開另一個(gè)窗口,多個(gè)窗口組合成的窗口還可以當(dāng)作一個(gè)窗口放入另一個(gè)窗口中,比如在
Word
中打開多個(gè)文檔就是這種情況。
Composite
模式的好處就是使得
clients
調(diào)用簡單,可以用一致的接口處理單個(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
模式的效果,簡化了
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ì)象“裝飾”起來,做一些“改造或者擴(kuò)展”,提供額外的功能,它只針對(duì)一個(gè)
class
。而
Composite
是一組“類似”的對(duì)象及其容納它們的容器一視同仁,使得
client
更簡單地處理單個(gè)對(duì)象和一組對(duì)象。它們目的不一樣。
?
實(shí)例:
Java IO library
中
BufferedReader
,
Reader
之間使用的就是
Decorator
模式,其中
BufferedReader
繼承自
Reader
,同時(shí)它內(nèi)部含有一個(gè)
Reader
引用,它是通過另一個(gè)
Reader
對(duì)象構(gòu)造而來,因此就為
Reader
提供更多的功能,如帶緩沖的
Reader
。使用非常簡單,只需要如此定義:
?
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)通知它們來更新,它們都是被動(dòng)地被通知,而不是主動(dòng)觀察。
?
???
實(shí)例:
MFC
里
CView
和
CDocument
之間就是一個(gè)觀察者模式,
CView
是
Observer
即觀察者,
CDocument
是
Observable
即被觀察者,當(dāng)
CDocument
修改后會(huì)自動(dòng)通知所有的
CView
對(duì)象來自動(dòng)更新它們顯示的內(nèi)容,這一點(diǎn)可以用
Word
很容易試出來。還有最新的
Windows
圖形界面框架
WinForm
中的窗口之間消息傳遞等用的也是
Observer
模式,一個(gè)窗口發(fā)生改變時(shí)會(huì)自動(dòng)通知所有與它有關(guān)系的窗口,來自動(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è)模式的好處顯而易見,就是在你設(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è)算法封裝起來,并使它們可以相互替換。
Stategy
使得算法給
client
使用的時(shí)候變得完全獨(dú)立。
?
???
Strategy
模式完全符合
OCP
(
Open-Closed Principle
,開閉原則),可以在不需要做
subclass
更不需要修改
base class
的情況下在
Runtime
的時(shí)候擴(kuò)展系統(tǒng)的功能,它不像
Template Method
需要
subclass
才能擴(kuò)展新的功能。
?
???
實(shí)例:
Strategy
典型的應(yīng)用是在薪資系統(tǒng)中,當(dāng)定義
Employee
類的時(shí)候,如果使用
Template Method
模式,就無法更改員工領(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é)同工作起來,而不至于因?yàn)閯e的不兼容的問題所影響。
?
???
Adapter
模式屬于結(jié)構(gòu)型模式,需要有
Adaptee
(被適配者)和
Adaptor
(適配器)兩個(gè)對(duì)象。一般情況下,我們通過繼承(
Inheritance
)或者合成(
Composition
)的方式擴(kuò)展類的功能后,會(huì)產(chǎn)生很多接口形式不同的類,但是我們想用同樣的方式調(diào)用它們,又不想改造它們(否則違反
OCP
原則),怎么辦呢?這種情況下就需要用到
Adapter
模式了,這些被“改造”的類就叫做
Adaptee
,而我們就需要寫個(gè)
Adaptor
類來轉(zhuǎn)調(diào)它們,把調(diào)用的接口改成統(tǒng)一的形式。
?
???
實(shí)例:
Java IO library
里的
InputStreamReader
就是一個(gè)
Adapter
,它負(fù)責(zé)將
InputStream
對(duì)象轉(zhuǎn)換為
Reader
對(duì)象的接口形式,使得用戶可以像使用其它
Reader
一樣的方式來使用
InputStream
對(duì)象,比如:
?
InputStreamReader isr = new InputStreamReader(
new FileInputStream("test.txt"));
??? BufferedReader br = new BufferedReader(isr);
??? String line = br.readLine();
?
???
這就做到了在不改造原來設(shè)計(jì)的類的接口的情況,擴(kuò)展了類的應(yīng)用范圍。一般情況下,
Adapter
模式需要結(jié)合
Factory
模式和
Proxy
模式來使用,使得接口的訪問更加一致性,更容易改造系統(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
模式我感覺是一個(gè)更高層次的
Adapter
,它是用來理順系統(tǒng)之間的關(guān)系,降低系統(tǒng)間的耦合度的常用方法,其實(shí)我們可能在設(shè)計(jì)系統(tǒng)的時(shí)候就在不知不覺地應(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ì)象封裝起來,
EJB
客戶端訪問
Session Bean
來代替訪問業(yè)務(wù)對(duì)象。這就大大簡化了
EJB
的系統(tǒng)結(jié)構(gòu),
Session Bean
就是相當(dāng)于一個(gè)外觀,也相當(dāng)于一個(gè)總管,把業(yè)務(wù)對(duì)象都管理起來,不讓客戶端直接“接觸”到它們。
?
???
《設(shè)計(jì)模式》一書中還有很多模式這里就不一一介紹了。其實(shí)我們大家都可以自己設(shè)計(jì)一種模式,但是肯定沒有
GoF
的那
23
個(gè)著名了。不過現(xiàn)在還有很多別的也很有名的模式,比如說
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
?
???
寫了這么多,終于寫完了,呵呵。寫完后,有兩點(diǎn)體會(huì):
1,
寫源碼剖析類的文章,就像是給代碼做翻譯,把代碼翻譯成圖片或文字。
2
,寫
OO/DP
之類理論的文章,則相當(dāng)于創(chuàng)造,不同的人有不同的體會(huì),技術(shù)學(xué)習(xí)層面不同的理解也不同。還是那句話:理論需要從實(shí)踐中來,多學(xué)習(xí)多實(shí)踐。歡迎指正!
?
最后我列幾本候捷老師推薦的書:
??? 1
,
C++
程序員必備的書(我以為,即時(shí)你不看,也建議備上,充門面也好,爭吵也好都很用)
?????? 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í)的書
?????? 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://www.shnenglu.com/javenstudio/
2006年12月28日