錯(cuò)誤/異常處理闸盔,一直是程序員痛恨,卻無法擺脫的夢(mèng)魘橙凳。如果一個(gè)系統(tǒng)中僅包含happy path
的實(shí)現(xiàn)蕾殴,那么這個(gè)系統(tǒng)的代碼規(guī)模會(huì)顯著縮小笑撞,而邏輯清晰度則大大增加。
C++
以及更加現(xiàn)代的語言钓觉,都提供了概念:異常茴肥。異常機(jī)制大大的簡(jiǎn)化了程序員的工作,并且讓實(shí)現(xiàn)代碼能夠更加關(guān)注真正的業(yè)務(wù)邏輯荡灾。
但異常機(jī)制并不是免費(fèi)的瓤狐,尤其是C++
的異常機(jī)制,對(duì)于空間和時(shí)間都有較大的消耗批幌。所以础锐,大多數(shù)實(shí)時(shí)性嵌入式系統(tǒng)都會(huì)禁止使用異常。于是荧缘,嵌入式程序員只好重新回到“一步一崗皆警,嚴(yán)密防衛(wèi)”的編程模式下。
即便允許使用異常截粗,只要一個(gè)類可能出現(xiàn)異常的狀況信姓,那么這個(gè)類的所有客戶都必須編寫相應(yīng)的異常處理代碼,這仍然是一個(gè)讓人不悅的工作绸罗。
錯(cuò)誤/異常就像強(qiáng)盜意推,一旦讓其闖入,你就需要不斷與其周旋:編寫防御代碼珊蟀,打印錯(cuò)誤日志菊值,DEBUG,等等……耗去你的時(shí)間和精力育灸,還把你原本漂亮的代碼搞的一團(tuán)糟腻窒。
所以,如果能夠在事先就杜絕一些錯(cuò)誤發(fā)生的可能描扯,那么程序員就無須為之付出相應(yīng)的精力定页,在保持系統(tǒng)簡(jiǎn)潔的同時(shí),也會(huì)讓系統(tǒng)更加健壯 (你能確保程序員的錯(cuò)誤處理代碼沒有錯(cuò)嗎绽诚?)典徊。
C++
作為一門強(qiáng)類型的靜態(tài)語言,我們要充分利用它的優(yōu)勢(shì)恩够。讓它的嚴(yán)格類型檢查作為我們忠實(shí)的看門狗卒落,在編譯時(shí)就可以辨認(rèn)出任何可能溜進(jìn)來的bug。從而不讓用戶存在犯錯(cuò)的可能(惡意用戶刻意的hack
除外)蜂桶。
錯(cuò)誤處理
基于防御式編程的思想儡毕,程序員需要在各處對(duì)可能的錯(cuò)誤進(jìn)行判斷和處理,從而為系統(tǒng)實(shí)現(xiàn)引入額外的復(fù)雜度,而這部分代碼有時(shí)候在系統(tǒng)中的占比是驚人的腰湾。很多系統(tǒng)都有測(cè)試覆蓋率的要求雷恃,但當(dāng)團(tuán)隊(duì)進(jìn)行系統(tǒng)測(cè)試時(shí),卻發(fā)現(xiàn)測(cè)試覆蓋率一旦達(dá)到某個(gè)比例時(shí)(我最近聽到的一個(gè)案例费坊,此比例是30%
)倒槐,再想提高這個(gè)比例就變得非常困難。通過工具一查看附井,發(fā)現(xiàn)大部分無法覆蓋的是錯(cuò)誤處理相關(guān)代碼讨越。而其中,有些代碼永遠(yuǎn)也無法得到運(yùn)行永毅,即我們常說的死代碼把跨。 讓我們看一個(gè)簡(jiǎn)單的例子:
struct Object
{
Status f1(Foo* foo)
{
if(foo == NULL)
{
// 或許能得到運(yùn)行的代碼(但也只是或許而已)
// 錯(cuò)誤處理:blah...blah...
return FAILED;
}
if(f2(foo) != SUCCESS)
{
// 死代碼
// 錯(cuò)誤處理:blah...blah...
}
// 其它代碼
// ...
return SUCCESS;
}
private:
Status f2(Foo* foo)
{
if(foo == 0)
{
// 死代碼
// blah...blah...
return FAILED;
}
value = foo->getValue() + 5;
return SUCCESS;
}
private:
int value;
// ...
};
出現(xiàn)這種問題的原因,一則是防衛(wèi)過度:即私有函數(shù)是否需要對(duì)傳入的空指針進(jìn)行防衛(wèi)沼死?事實(shí)上着逐,將那些死代碼刪除,在任何情況下意蛀,都不會(huì)讓系統(tǒng)更不安全滨嘱。 而清理后的結(jié)果,卻會(huì)比原來要清晰和簡(jiǎn)潔許多浸间。
但那些遵從懷疑一切哲學(xué)的謹(jǐn)慎編程者,對(duì)這種做法持反對(duì)態(tài)度吟榴。他們強(qiáng)烈認(rèn)同必須通過考驗(yàn)才可獲得信任的價(jià)值觀魁蒜。他們認(rèn)為:私有函數(shù)也是函數(shù)。而作為函數(shù)吩翻,它不應(yīng)該知道究竟誰是它的客戶兜看,因此也不應(yīng)該對(duì)客戶給予信任。更何況狭瞎,代碼是隨時(shí)可以改變的细移,當(dāng)前的用戶能夠保證其安全性,并不意味這隨后的新增客戶也會(huì)同樣遵守熊锭。
我們先不去討論這樣的觀點(diǎn)是否正確弧轧。重要的是,無論你屬于那一派碗殷,你都肯定會(huì)同意:如果一件事不可能出錯(cuò)精绎,你就不會(huì)再為之焦慮。
指針 vs. 引用
在C
時(shí)代锌妻,指針是間接訪問對(duì)象的唯一方式代乃。由于任何一個(gè)指針都可是是空指針,謹(jǐn)慎的程序員會(huì)在獲取到一個(gè)指針時(shí)仿粹,都會(huì)先判斷其是否為空搁吓。這種代碼在任何一個(gè)正式的C
項(xiàng)目中比比皆是原茅。
到了C++
時(shí)代,為了避免這類問題堕仔,一種新的對(duì)象訪問方式誕生了:引用擂橘。不幸的是,由于很多C++
程序員都是從C
工程師轉(zhuǎn)化而來的贮预,所以贝室,很多項(xiàng)目仍然在固執(zhí)的大量使用指針。比如:
Status f(Object* object)
{
if(object == 0) {
// 錯(cuò)誤處理代碼:記日志,發(fā)告警等等仿吞。
return FAILED;
}
// 對(duì) object 所指對(duì)象進(jìn)行操作
object->f();
// ...
}
和指針不同滑频,引用是對(duì)象的別名。因此唤冈,引用具有如下性質(zhì):
- 引用在定義時(shí)即需要賦值;
- 一旦賦值峡迷,你不可能讓其引用其它對(duì)象;
- 對(duì)引用進(jìn)行取地址操作,就是對(duì)對(duì)象進(jìn)行取地址操作你虹。
所以绘搞,一旦將參數(shù)改為引用方式,你就得到了傳入對(duì)象非空的承諾傅物,代碼會(huì)一下子簡(jiǎn)潔許多:
Status f(Object& object)
{
object.f();
// ...
}
空引用
引用的非空特性給我們帶來很大的便利夯辖。不幸的是,現(xiàn)實(shí)在理想面前總是很骨感:空引用的的確確存在董饰。比如:
void f(Object& object);
Object* object = 0;
// 空引用!
f(*object);
這個(gè)殘酷的現(xiàn)實(shí)蒿褂,動(dòng)搖了我們的信念:使用指針還是引用,似乎并沒有本質(zhì)差別卒暂,而引用還有那么一堆約束和限制啄栓,還是使用指針更好。
事前條件
但是也祠,從契約式編程(Design By Contract
)的角度看昙楚,一旦一個(gè)函數(shù)的某個(gè)參數(shù)聲明為引用類型,那就意味著它明確的定義了一個(gè)事前條件(Precondition
):函數(shù)的調(diào)用者有義務(wù)確保傳入的引用非空诈嘿。否則堪旧,空引用引起的崩潰應(yīng)該由調(diào)用者負(fù)責(zé)。
如果你仍然對(duì)此持有異議奖亚,那么不妨設(shè)想一下:讓我們重新回到指針時(shí)代崎场,看看會(huì)發(fā)生什么。
指針事實(shí)上有三種狀態(tài):
- 空:空指針
- 有效:指向一個(gè)合法對(duì)象
- 非法:指向一個(gè)未知世界
而引用卻只有兩種狀態(tài):
- 有效:引用了一個(gè)合法對(duì)象
- 非法:引用了一個(gè)非法對(duì)象(包括空對(duì)象)
如果一個(gè)函數(shù)的某個(gè)參數(shù)為指針類型遂蛀,那么作為函數(shù)的實(shí)現(xiàn)者谭跨,你也只能檢測(cè)指針是否為空,卻永遠(yuǎn)無法保證客戶是否會(huì)傳入一個(gè)非法指針。所以螃宙,保證指針的合法性(無論是否為空)蛮瞄,當(dāng)然是調(diào)用者的責(zé)任。
由此不難得出結(jié)論:保證一個(gè)引用不會(huì)是非法引用也應(yīng)該是調(diào)用者的責(zé)任谆扎。
事后條件
而當(dāng)一個(gè)函數(shù)的返回值是一個(gè)引用時(shí)挂捅,這樣的聲明則明確定義了一個(gè)事后條件(Post Condition
)。而保證引用的合法性堂湖,就成了函數(shù)實(shí)現(xiàn)者的義務(wù)闲先。
// 函數(shù)實(shí)現(xiàn)者必須保證返回值的合法性
Object& f();
所以,實(shí)現(xiàn)者必須保證返回的對(duì)象不能是個(gè)臨時(shí)對(duì)象无蜂。比如:
Object& getObject()
{
// 不要這樣做
Object object;
// ...
return object;
}
所以伺糠,也要避免可能造成內(nèi)存泄漏。比如:
Object& getObject()
{
return *(new Object);
}
而返回引用的優(yōu)勢(shì)則在于:它讓所有的客戶代碼無須再進(jìn)行相關(guān)的錯(cuò)誤處理斥季。
// 無須判斷空指針
getObject().f();
何時(shí)必須使用指針
由于引用比指針更加嚴(yán)格训桶,所以,并非在所有場(chǎng)景下引用都可以代替指針酣倾。下面列出了幾種常見理由(但并非全部)舵揭。
空指針是一種合法狀態(tài)
指針與引用貌似很像,但事實(shí)上存在在本質(zhì)的語義差別:
- 指針是
Maybe
或Optional
語義躁锡; - 引用是
Value
語義午绳。
因而,一些時(shí)候映之,空指針也是一種合法狀態(tài)(正如Maybe
語義的Nothing
一樣)箱叁,比如,單向鏈表中最后一個(gè)節(jié)點(diǎn)的next
指針惕医;一顆樹Root
節(jié)點(diǎn)的 parent
指針。比如:
struct Node
{
// ...
private:
// 作為 Root 節(jié)點(diǎn),parent 可能為空 Node* parent;
// 作為 Leaf 節(jié)點(diǎn),children 可能為空 Node* leftChild;
Node* rightChild;
};
這種情況下算色,我們只能使用指針抬伺,而不能使用引用。
需變更所指對(duì)象
如果一個(gè)指針在其生命周期內(nèi)需要改變所指向的對(duì)象灾梦。比如:
struct Foo
{
Foo(Object* object) : object(object) {}
// 變更接口
void setObject(Object* object)
{
this->object = object;
}
// ...
private:
// 只能使用指針
Object* object;
};
作為輸出參數(shù)峡钓,需要得到一個(gè)對(duì)象
函數(shù)希望通過某個(gè)輸出參數(shù)以取得一個(gè)對(duì)象,比如:
Status getObject(int key, Object** object)
{
// ...
(*object) = findObjectByKey(key);
if(*object == 0)
{
return NOTFOUND;
}
// ...
return SUCCESS;
}
當(dāng)然若河,當(dāng)通過返回值來返回對(duì)象時(shí)能岩,如果存在查找不到的可能性,也應(yīng)該使用指針:
Object* getObject(int key)
{
Object* object = findObjectByKey(key);
if(object == 0)
{
return 0;
}
// ...
return object;
}
除非你通過空對(duì)象模式萧福,避免空指針的可能性:
Object& getObject(int key)
{
Object* object = findObjectByKey(key);
if(object == 0)
{
return NullObject::getInstance();
}
// ...
return *object;
}
動(dòng)態(tài)分配內(nèi)存 vs. 靜態(tài)分配
單例模式(Singleton
)估計(jì)是OO
程序員使用最為廣泛的設(shè)計(jì)模式拉鹃。一般而言,從標(biāo)準(zhǔn)的教科書上,Singleton
的實(shí)現(xiàn)模式如下:
struct Foo
{
static Foo* getInstance();
// public functions
private:
Foo();
private:
static Foo* foo;
};
Foo* Foo:foo = 0;
Foo* Foo::getInstance()
{
if(foo != 0) return foo;
foo = new Foo();
return foo;
}
這樣的實(shí)現(xiàn)方式至少有兩個(gè)問題:
- 內(nèi)存分配失敗的可能膏燕;
- 由于內(nèi)存分配可能失敗钥屈,所有的客戶代碼為了安全,都必須檢查
getInstance()
是否返回空指針坝辫。
這無疑會(huì)增加客戶代碼的復(fù)雜度篷就。為了避免這類問題,我們必須從源頭消滅這樣的可能性:將內(nèi)存分配從動(dòng)態(tài)改為靜態(tài)分配:
struct Foo
{
static Foo& getInstance();
// public functions
private:
Foo();
};
Foo& Foo::getInstance()
{
static Foo foo;
return foo;
}
這樣的實(shí)現(xiàn)方式可以帶來諸多好處:
- 由于內(nèi)存是靜態(tài)分配的近忙,一旦系統(tǒng)成功加載竭业,就永遠(yuǎn)也不可能失敗,因而可以將返回值類型改為引用及舍;
- 這種局部靜態(tài)分配的方式未辆,讓實(shí)例的構(gòu)造是
lazy
的,這可以有效避免C++
靜態(tài)成員初始化順序不確定帶來的問題击纬。 - 對(duì)于
getInstance
函數(shù)的實(shí)現(xiàn)鼎姐,由于不需要每次都判斷指針是否為空,甚至?xí)硇阅苌系囊恍┖锰幐瘛#呐聦?duì)于性能影響微乎其微炕桨,但也要遵守不做不必要的劣化原則); - 對(duì)于客戶肯腕,杜絕了指針為空的可能性献宫,可以編寫更加簡(jiǎn)潔的代碼,避免思考空指針處理的邏輯实撒,甚至也會(huì)對(duì)性能有些好處(邏輯同上)姊途。
枚舉 vs. 投幣模式
假設(shè)現(xiàn)在有個(gè)需求,要求你實(shí)現(xiàn)一個(gè)長(zhǎng)度類知态。類的使用者捷兰,可以使用英里 (Mile
),碼(Yard
)和英尺(Feet
)為單位來表現(xiàn)一個(gè)長(zhǎng)度负敏。并且可以比較任意兩個(gè)長(zhǎng)度是否相等贡茅。
typedef unsigned int Amount;
enum Unit {
FEET = 1,
YARD = 3 * FEET,
MILE = 1760 * YARD
};
struct Length
{
Length(const Amount& amount, const Unit unit) : amountInBaseUnit(unit * amount)
{}
bool operator==(const Length& another) const
{
return amountOfBaseUnit == another.amountOfBaseUnit;
}
private:
Amount amountOfBaseUnit;
};
這個(gè)實(shí)現(xiàn)非常簡(jiǎn)潔。它使用枚舉來紀(jì)錄長(zhǎng)度單位之間的轉(zhuǎn)換系數(shù)其做,無論使用者以何單位來紀(jì)錄一個(gè)長(zhǎng)度顶考,Length
總是會(huì)在構(gòu)造時(shí)將其轉(zhuǎn)化為以基準(zhǔn)單位為單位的數(shù)量,而基準(zhǔn)單位在這里很明顯是FEET妖泄。
但這個(gè)實(shí)現(xiàn)的一個(gè)重大問題是:由于在C++ 98
里驹沿,枚舉并非強(qiáng)類型。事實(shí)上使用者可以傳入任何整數(shù)作為 unit
參數(shù)蹈胡。
基于防御式編程的哲學(xué)渊季,我們需要處理這種情況朋蔫。所以,一種做法是在構(gòu)造函數(shù)里進(jìn)行檢查梭域,如果傳入一個(gè)非法的unit
斑举,就拋出異常。
struct Length
{
Length(const Amount& amount, const Unit unit) : amountInBaseUnit(unit * amount)
{
if(unit != MILE && unit != YARD && unit != FEET)
{
throw InvalidUnitException;
}
}
// ...
private:
Amount amountOfBaseUnit;
};
這個(gè)實(shí)現(xiàn)的主要問題是:構(gòu)造函數(shù)可能會(huì)拋出異常病涨,因而客戶代碼必須處理此異常富玷。更何況,在嵌入式系統(tǒng)上既穆,異常往往是不允許使用的赎懦。因而必須額外提供一個(gè)額外的init
函數(shù)以允許返回失敗。
當(dāng)然幻工,你還可以進(jìn)行其它方式的實(shí)現(xiàn)励两,來應(yīng)對(duì)上述問題。但無論你怎樣做囊颅,只要Unit
是個(gè)枚舉当悔,你總是逃不脫要對(duì)錯(cuò)誤進(jìn)行檢測(cè)和處理。然后把你原有的簡(jiǎn)潔設(shè)計(jì)搞的一團(tuán)糟踢代。
避免處理錯(cuò)誤的最好方式是不要讓錯(cuò)誤有發(fā)生的可能性盲憎。所以我們將實(shí)現(xiàn)方式改為:
struct Unit
{
// 允許用戶使用的 Unit 對(duì)象
static Unit getMile() { return MILE_FACTOR; }
static Unit getYard() { return YARD_FACTOR; }
static Unit getFeet() { return FEET_FACTOR; }
Amount toAmountInBaseUnit(const Amount& amount) const
{
return conversionFactor * amount;
}
private:
// 構(gòu)造函數(shù)私有以避免用戶實(shí)例化 Unit 對(duì)象
Unit(unsigned int conversionFactor)
: conversionFactor(conversionFactor)
{}
private:
enum
{
FEET_FACTOR = 1,
YARD_FACTOR = 3 * FEET_FACTOR,
MILE_FACTOR = 1760 * YARD_FACTOR
};
private:
unsigned int conversionFactor;
};
// 通過宏定義,提供和原來一樣的 Unit 使用方式。
#define MILE Unit::getMile()
#define YARD Unit::getYard()
#define FEET Unit::getFeet()
struct Length
{
Length(const Amount& amount, const Unit& unit)
: amountInBaseUnit(unit.toAmountInBaseUnit(amount))
{}
// ...
private:
Amount amountOfBaseUnit;
};
這種處理問題的手段胳挎,被稱為投幣模式(Slug Pattern
)饼疙。
類復(fù)用 vs. 模板復(fù)用
現(xiàn)在,我們?cè)谏厦娴睦踊A(chǔ)上增加一個(gè)新需求:實(shí)現(xiàn)一個(gè)容量類慕爬。此類的使用者窑眯,可以使用盎司(OZ
)和湯匙(TBSP
)為單位來表現(xiàn)一個(gè)容量。并且可以比較任意兩個(gè)容量是否相等医窿。
不難看出磅甩,容量和長(zhǎng)度的需求非常相似。所以姥卢,它們的代碼是可以復(fù)用的卷要。于是,我們將這兩個(gè)體系合二為一隔显,起一個(gè)更通用的名字:Quantity
。
struct Quantity
{
Quantity(const Amount& amount, const Unit& unit)
: amountInBaseUnit(unit.toAmountInBaseUnit(amount))
{}
bool operator==(const Quantity& another) const
{
return amountOfBaseUnit == another.amountOfBaseUnit;
}
private:
Amount amountOfBaseUnit;
};
struct Unit
{
// 允許用戶使用的長(zhǎng)度 Unit 對(duì)象
static Unit getMile() { return MILE_FACTOR; }
static Unit getYard() { return YARD_FACTOR; }
static Unit getFeet() { return FEET_FACTOR; }
// 允許用戶使用的容量 Unit 對(duì)象
static Unit getTbsp() { return TBSP_FACTOR; }
static Unit getOz() { return OZ_FACTOR; }
// ...
private:
enum
{
FEET_FACTOR = 1,
YARD_FACTOR = 3
MILE_FACTOR = 1760 * YARD_FACTOR
};
enum
{
TBSP_FACTOR = 1,
OZ_FACTOR = 2 * TBSP_FACTOR,
};
private:
unsigned int conversionFactor;
};
#define MILE Unit::getMile()
#define YARD Unit::getYard()
#define FEET Unit::getFeet()
#define OZ Unit::getOz()
#define TBSP Unit::getTbsp()
我們現(xiàn)在通過一套類就解決了兩個(gè)體系的問題饵逐,我們不僅為自己對(duì)DRY原則的堅(jiān)持和強(qiáng)大的抽象能力感到驕傲括眠。
但沒過多久,你就會(huì)收到一個(gè)來自于用戶的Bug
報(bào)告: 1 FEET
怎能等于 1 TBST
呢?
哦倍权,長(zhǎng)度和容量是兩種不同的體系掷豺。我們?cè)瓉硗嗽谂袛嘞嗟葧r(shí)對(duì)兩種體系進(jìn)行區(qū)分捞烟。
作為富有經(jīng)驗(yàn)的程序員,這個(gè)難不倒我們:只需要為Unit
添加一個(gè)類型当船,對(duì)兩種不同的Unit
進(jìn)行區(qū)分即可题画。
struct Unit
{
static Unit getMile() { return Unit(MILE_FACTOR, LENGTH_TYPE); }
// ...
static Unit getOz() { return Unit(OZ_FACTOR, VOLUME_TYPE); }
// 增加一個(gè)判斷 UnitType 是否一致的借口
bool hasSameType(const Unit& another) const
{
return unitType == another.unitType;
}
// ...
private:
Unit(unsigned int conversionFactor, UnitType unitType)
: conversionFactor(conversionFactor)
, unitType(unitType)
{}
// 增加枚舉類型:UnitType
enum UnitType
{
LENGTH_TYPE,
VOLUME_TYPE
};
// ...
private:
unsigned int conversionFactor; UnitType unitType;
};
然后,我們將Quantity
的實(shí)現(xiàn)改為下面的樣子:
struct Quantity
{
Quantity(const Amount& amount, const Unit& unit)
: amount(amount)
, unit(unit)
{}
bool operator==(const Quantity& another) const
{
// 先判斷兩個(gè) Unit 是否屬于同一類型
return unit.hasSameType(another.unit) &&
getAmountInBaseUnit() == another.getAmountInBaseUnit();
}
private:
Amount getAmountInBaseUnit() const
{
return unit.toAmountInBaseUnit(amount);
}
private:
Amount amount; Unit unit;
};
這樣做總算滿足了要求德频,盡管其背后的語義仍然略顯怪異(為什么允許一個(gè)長(zhǎng)度和一個(gè)容量對(duì)比相等性?)苍息。但如果我們現(xiàn)在增加一個(gè)新的需求:加法運(yùn)算,其實(shí)現(xiàn)方式將會(huì)變得非常棘手壹置。
Quantity Quantity::operator+(const Quantity& another)
{
if(not unit.hasSameType(another)) {
// 該如何做? 拋出異常? 還是返回一個(gè)非法的 Quantity?
}
// ...
}
而避免這種問題的方法是:禁止兩個(gè)不同體系的Quantity
之間進(jìn)行任何互操作竞思。而模板可以幫助我們達(dá)到目標(biāo):
template <typename UNIT>
struct Quantity
{
Quantity(const Amount& amount, const UNIT& unit)
: amountInBaseUnit(UNIT.toAmountInBaseUnit(amount))
{}
bool operator==(const Quantity& another) const
{
return getAmountInBaseUnit() == another.getAmountInBaseUnit();
}
Quantity operator+(const Quantity& another)
{
return amountInBaseUnit + another.amountInBaseUnit;
}
private:
Quantity(const Amount& amountInBaseUnit)
: amountInBaseUnit(amountInBaseUnit)
{}
private:
Amount amountInBaseUnit;
};
由于兩個(gè)體系的Unit
的算法和數(shù)據(jù)都完全一樣,所以這里我們?yōu)樗鼈兲崛∫粋€(gè)基類钞护。其中盖喷,我們將Unit
的構(gòu)造函數(shù)設(shè)為protected
的,以避免用戶直接創(chuàng)建Unit
實(shí)例难咕。
struct Unit
{
Amount toAmountInBaseUnit(const Amount& amount) const;
protected:
Unit(unsigned int conversionFactor)
: conversionFactor(conversionFactor)
{}
private:
unsigned int conversionFactor;
};
然后课梳,我們讓LengthUnit
和VolumeUnit
分別繼承自Unit
:
struct LengthUnit : Unit
{
// 允許用戶使用的長(zhǎng)度 Unit 對(duì)象
static LengthUnit getMile() { return MILE_FACTOR; }
static LengthUnit getYard() { return YARD_FACTOR; }
static LengthUnit getFeet() { return FEET_FACTOR; }
private:
enum
{
FEET_FACTOR = 1,
YARD_FACTOR = 3 * FEET_FACTOR,
MILE_FACTOR = 1760 * YARD_FACTOR
};
};
struct VolumeUnit : Unit
{
static Unit getTbsp() { return TBSP_FACTOR; }
static Unit getOz() { return OZ_FACTOR; }
private:
enum
{
TBSP_FACTOR = 1,
OZ_FACTOR = 2 * TBSP_FACTOR
};
};
然后我們用LengthUnit
和VolumeUnit
分別實(shí)例化Quantity
模板,從而得到兩個(gè)僅僅共享代碼余佃,卻無法相互操作的兩個(gè)類:Length
和Volume
暮刃。
typedef Quantity<LengthUnit> Length;
typedef Quantity<VolumeUnit> Volume;
另外,通過這樣的方法咙冗,我們不僅避免了兩種體系之間互操作的問題沾歪,還將兩個(gè)體系從代碼上也清晰的分割為相互獨(dú)立的兩個(gè)部分。
總結(jié)
本文通過幾個(gè)不同的C++
語言的例子,來說明預(yù)防勝于治療思想對(duì)于提高系統(tǒng)健壯性的重要性。針對(duì)不同的語言敢订,解決方案或許不同铆遭,但本文在各個(gè)例子中思考過程才是本文更想給讀者帶來的價(jià)值。