原文是C++ VIEW第二期的一篇譯文,這裡做個總結,便於查閱。
開放封閉原則
系統在添加新的需求的時候能夠儘可能做到,只是添加新的代碼 open for extension,而不需要修改原有模組代碼 closed for modification
通過提取基類的方法,client 調用server 抽象基類abstract server的抽象介面,從而更換不同sever的時候,client的調用server的代碼都不需要改動,介面不變,
只是server內容變化。
例子,
一個繪製函數,要求能夠針對輸入的不同對象,調用不同的繪製函數,如能夠繪製矩形,圓形,適當調用矩形繪製函數,圓形繪製函數。
1.用c語言實現
這個例子其實給出了,c語言類比c++類繼承的方法。利用指標的強制轉換,因為指標僅僅是地址可以指向任何對象,利用指標強制轉換,告訴編譯器具體按什麼對象處理指標所指。
Listing 1
/*Procedural Solution to the Square/Circle Problem*/
enum ShapeType {circle, square};
struct Shape
{
ShapeType itsType;
};
struct Circle
{
ShapeType itsType;
double itsRadius;
Point itsCenter;
};
struct Square
{
ShapeType itsType;
double itsSide;
Point itsTopLeft;
};
//
// 下面兩個函數的實現定義在別處
//
void DrawSquare(struct Square*)
void DrawCircle(struct Circle*);
typedef struct Shape *ShapePointer;
void DrawAllShapes(ShapePointer list[], int n)
{
int i;
for (i=0; i<n; i++)
{
struct Shape* s = list[i];
switch (s->itsType)
{
case square:
DrawSquare((struct Square*)s);
break;
case circle:
DrawCircle((struct Circle*)s);
break;
}
}
}
上面的代碼不符合open close法則,因為新加入其它的shape如橢圓, DrawAllShapes函數就需要變化。
2. C++的實現
Listing 2
/*OOD solution to Square/Circle problem.*/
class Shape
{
public:
virtual void Draw() const = 0;
};
class Square : public Shape
{
public:
virtual void Draw() const;
};
class Circle : public Shape
{
public:
virtual void Draw() const;
};
void DrawAllShapes(Set<Shape*>& list)
{
for (Iterator<Shape*>i(list); i; i++)
(*i)->Draw();
}
和上面C語言實現代碼對比,顯然符合open close 法則,加入新的shape, DrawAllShapes函數可保持不變,只是添加新的shape內容。
但是事實上如果有新的需求變化,DrawAllShapes也無法做到完全不變,任何模組只能是相對封閉,無法完全封閉。
例如我們有新的需求,要求繪製圖形列表的時候,一種形狀的圖形要在另一種圖形前面繪製。
解決方案,加入 順序抽象類別
Listing 3
/*Shape with ordering methods.*/
class Shape
{
public:
virtual void Draw() const = 0;
virtual bool Precedes(const Shape&) const = 0;
bool operator<(const Shape& s) {return Precedes(s);}
};
Listing 4
/*DrawAllShapes with Ordering*/
void DrawAllShapes(Set<Shape*>& list)
{
// copy elements into OrderedSet and then sort.
OrderedSet<Shape*> orderedList = list;
orderedList.Sort();
for (Iterator<Shape*> i(orderedList); i; i++)
(*i)->Draw();
}
Listing 5
/*Ordering a Circle*/
bool Circle::Precedes(const Shape& s) const
{
if (dynamic_cast<Square*>(s))
return true;
else
return false;
}
這裡使用的Precedes函數,如果新加入shape需要改變,怎麼樣才能做到更好呢?
使用資料驅動獲得封閉性,利用預先寫好的table,我們將各個圖形的優先順序寫入table,那麼新加入shape只需要更新table加入新的shape。
Listing 6
/*Table driven type ordering mechanism*/
#include <typeinfo.h>
#include <string.h>
enum {false, true};
typedef int bool;
class Shape
{
public:
virtual void Draw() const = 0;
virtual bool Precedes(const Shape&) const;
bool operator<(const Shape& s) const
{return Precedes(s);}
private:
static char* typeOrderTable[];
};
/*
譯者註:由於typeinfo.name沒有標準,因此最好直接用typeinfo作為表中的元素類型,而不是用類名字串。
*/
char* Shape::typeOrderTable[] =
{
“Circle”,
“Square”,
0
};
// This function searches a table for the class names.
// The table defines the order in which the
// shapes are to be drawn. Shapes that are not
// found always precede shapes that are found.
//
bool Shape::Precedes(const Shape& s) const
{
const char* thisType = typeid(*this).name();
const char* argType = typeid(s).name();
bool done = false;
int thisOrd = -1;
int argOrd = -1;
for (int i=0; !done; i++)
{
const char* tableEntry = typeOrderTable[i];
if (tableEntry != 0)
{
if (strcmp(tableEntry, thisType) == 0)
thisOrd = i;
if (strcmp(tableEntry, argType) == 0)
argOrd = i;
if ((argOrd > 0) && (thisOrd > 0))
done = true;
}
else // table entry == 0
done = true;
}
return thisOrd < argOrd;
}
進一步擴充封閉性
故事還沒有結束。我們已經設法使得Shape類層次和DrawShapes函數對於依賴於圖形類型的畫出順序是封閉的。然而,如果畫出順序與圖形類型無關,那麼Shape衍生類別並不對這種順序的變化封閉。我們似乎需要根據一個更加高層次的結構來決定畫出各個shape的順序。關於這個問題的深入徹底探討已經超過了本文的範圍;然而有興趣的讀者可能會考慮定義一個OrderedObject的抽象類別,並從Shape類和OrderedObject類派生一個新的抽象類別OrderedShape。
所有成員變數都應該是私人的
永遠不要用全域變數
然而,有些情況下全域變數的方便性是很重要的。全域變數cout和cin就是例子。在這種情況下,如果沒有破環開放―封閉(open-closed)原則,那麼犧牲風格來獲得這種方便性是值得的
RTTI是危險的
根據一般的經驗,如果使用RTTI不會破壞開放―封閉(open-closed)原則,那麼就是安全的