本系列的文章,來介紹編程中的設計模式,介紹的內容主要為《大話設計模式》的讀書筆記,并改用C++語言來實現(xiàn)(書中使用的是.NET中的C#),本篇來學習第一章,介紹的設計模式是——簡單工廠模式。
1 面向對象編程
設計模式依賴與面向對象編程密不可分,因此在開始學習設計模式之前,先簡單介紹下面向對象編程。
先來看一個小故事:
話說三國時期,曹操在赤壁帶領百萬大軍,眼看就要滅掉東吳,統(tǒng)一天下,非常高性,于是大宴文武。
在酒席間,不覺吟到:“喝酒唱歌,人生真爽,......”,眾文武齊呼:“丞相好詩!”,
于是一臣子速速命令印刷工匠進行刻版印刷,以便流傳天下。
印刷工匠刻好樣張,拿出來給曹操一看,曹操感覺不妥,
說道:“喝與唱,此話過俗,應該改為對酒當歌較好!”,
于是臣子就命令工匠重新來過,工匠眼看連夜刻版之功,徹底白費,心中叫苦不迭,只得照辦。
印刷工匠再次刻好樣張,拿出來給曹操過目,曹操細細一品,覺得還是不好,
說:“人生真爽太過直接,應該改為問句才夠意境,因此應改為對酒當歌,人生幾何”,
當臣子再次轉告工匠之時,工匠暈倒......
那,問題出在哪里呢?
大概是三國時期還沒有活字印刷術吧,所以要改字的時候,就必須整個刻板全部重新雕刻。
如果有了活字印刷術,其實只需要更改四個字即可,其余工作都未白做。
我們聯(lián)想編程,從這個小故事中,來體會一下編程中的一些思想:
- 可維護:要改字,只需更改需要變動的字即可可復用:這些字并不是只是這次有用,后續(xù)如果在其它印刷中需要用,可重復使用可擴展:如果詩中需要加字,只需另外單獨刻字即可靈活性:字的排列可以橫排,也可以豎排
面向對象編程,通過封裝、繼承和多態(tài),把程序的耦合度降低。
傳統(tǒng)印刷術的問題就在于把所有字都刻在同一個版面上的耦合度太高。
使用設計模式可以使程序更加靈活,容易修改,并易于復用。
2 計算器實例
下面以一個計算器的代碼實例,來體會封裝的思想,以及簡單工廠模式的使用。
題目:設計一個計算器控制臺程序,輸入為兩個數(shù)和運算符,輸出結果
功能比較簡單,先來看第一個版本的實現(xiàn)。
2.1 版本一:面向過程
第一個版本采用面向過程的思想,從接收用戶輸入,到數(shù)據(jù)運算,以及最后的輸出,都是按順序在一個代碼塊中實現(xiàn)的:
int main()
{
float numA = 0;
float numB = 0;
float result = 0;
char operate;
bool bSuccess = true;
printf("please input a num A:n");
scanf("%f", &numA);
printf("please input a operate(+ - * ):n");
std::cin >> operate;
printf("please input a num B:n");
scanf("%f", &numB);
switch(operate)
{
case '+':
{
result = numA + numB;
break;
}
case '-':
{
result = numA - numB;
break;
}
case '*':
{
result = numA * numB;
break;
}
case '/':
{
if (numB == 0)
{
bSuccess = false;
printf("divisor cannot be 0!n");
break;
}
result = numA / numB;
break;
}
default:
{
bSuccess = false;
break;
}
}
if (bSuccess)
{
printf("%f %c %f = %fn", numA, operate, numB, result);
}
else
{
printf("[%f %c %f] calc fail!n", numA, operate, numB);
}
return 0;
}
該程序的運行效果如下圖所示:
上述代碼實現(xiàn)本身沒有什么問題,但是,如果現(xiàn)在要再實現(xiàn)一個帶有UI界面的計算器,代碼能不能復用呢?很顯然不行,代碼都是在一起的。
因此,為了便于代碼復用,可以將計算部分的代碼和顯示部分的代碼分開,降低它們之間的耦合度。
2.2 版本二:對業(yè)務封裝
版本二則是對計算部分的業(yè)務代碼和顯示部分的控制臺輸入輸出代碼分開。
計算部分的業(yè)務代碼,設計一個Operation運算類,通過其成員函數(shù)GetResult來實現(xiàn)加減乘除運算。
2.2.1 業(yè)務代碼
class Operation
{
public:
bool GetResult(float numA, float numB, char operate, float &result)
{
bool bSuccess = true;
switch(operate)
{
case '+':
{
result = numA + numB;
break;
}
case '-':
{
result = numA - numB;
break;
}
case '*':
{
result = numA * numB;
break;
}
case '/':
{
if (numB == 0)
{
bSuccess = false;
printf("divisor cannot be 0!n");
break;
}
result = numA / numB;
break;
}
default:
{
bSuccess = false;
break;
}
}
return bSuccess;
}
};
2.2.2 控制臺界面代碼
顯示部分的控制臺輸入輸出代碼,還在main函數(shù)中。
int main()
{
float numA = 0;
float numB = 0;
float result = 0;
char operate;
printf("please input a num A:n");
scanf("%f", &numA);
printf("please input a operate(+ - * ):n");
std::cin >> operate;
printf("please input a num B:n");
scanf("%f", &numB);
Operation Op1;
bool bSuccess = Op1.GetResult(numA, numB, operate, result);
if (bSuccess)
{
printf("%f %c %f = %fn", numA, operate, numB, result);
}
else
{
printf("[%f %c %f] calc fail!n", numA, operate, numB);
}
return 0;
}
版本二的運行效果演示如下:
上述的版本二的代碼實現(xiàn),就用到了面向對象三大特性中的封裝。
那,上述代碼,是否可以做到靈活擴展?
比如,如果希望增加一個開根號的運算,如果改?
按照現(xiàn)有邏輯,需要修改Operation運算類,在switch中增加一個分支。但這樣,會需要加減乘除的邏輯再次參與編譯,另外,如果在修改開根號的代碼時,不小心改動了加減乘除的邏輯,影響就大了。
因此,可以使用面向對象中繼承和多態(tài)的思想,來實現(xiàn)各個運算類的分離。
2.3 版本三:簡單工廠
版本三用到了封裝、繼承、多態(tài),以及通過簡單工廠來實例化出合適的對象。
2.3.1 Operation運算類(父類)
Operation運算類為一個抽象類,是加減乘除類的父類。
該類包含numA和numB兩個成員變量,以及一個虛函數(shù)GetResult用于計算運算結果,各個子類中對其進行具體的實現(xiàn)。
// 操作類(父類)
class Operation
{
public:
float numA = 0;
float numB = 0;
public:
virtual float GetResult()
{
return 0;
};
};
2.3.2 加減乘除類(子類)
加減乘除子類通過公有繼承Operation類,可以訪問其共有成員變量numA和numB,并對GetResult方法進行具體的實現(xiàn):
// 加法類(子類)
class OperationAdd : public Operation
{
public:
float GetResult()
{
return numA + numB;
}
};
// 減法類(子類)
class OperationSub : public Operation
{
public:
float GetResult()
{
return numA - numB;
}
};
// 乘法類(子類)
class OperationMul : public Operation
{
public:
float GetResult()
{
return numA * numB;
}
};
// 除法類(子類)
class OperationDiv : public Operation
{
public:
float GetResult()
{
if (numB == 0)
{
printf("divisor cannot be 0!n");
return 0;
}
return numA / numB;
}
};
2.3.3 簡單運算工廠類
為了能方便地實例化加減乘除類,考慮使用一個單獨的類來做這個創(chuàng)造實例的過程,這個就是工廠。
設計一個OperationFactory類來實現(xiàn),這樣,只要輸入運算的符號,就能實例化出合適的對象。
// 簡單工廠模式
class OperationFactory
{
public:
Operation *createOperation(char operation)
{
Operation *oper = nullptr;
switch(operation)
{
case '+':
{
oper = (Operation *)(new OperationAdd());
break;
}
case '-':
{
oper = (Operation *)(new OperationSub());
break;
}
case '*':
{
oper = (Operation *)(new OperationMul());
break;
}
case '/':
{
oper = (Operation *)(new OperationDiv());
break;
}
default:
{
break;
}
}
return oper;
}
};
使用版本三,如果后續(xù)需要修改加法運算,只需要修改OperationAdd類中的內容即可,不會影響到其它計算類。
2.3.4 控制臺界面代碼
顯示部分的控制臺輸入輸出代碼,還在main函數(shù)中。
通過多態(tài),返回父類的方式,實現(xiàn)對應運算的計算結果。
{
float numA = 0;
float numB = 0;
float result = 0;
char operate;
printf("please input a num A:n");
scanf("%f", &numA);
printf("please input a operate(+ - * ):n");
std::cin >> operate;
printf("please input a num B:n");
scanf("%f", &numB);
OperationFactory opFac;
Operation *oper = nullptr;
oper = opFac.createOperation(operate);
if (oper != nullptr)
{
oper->numA = numA;
oper->numB = numB;
result = oper->GetResult();
printf("%f %c %f = %fn", numA, operate, numB, result);
delete oper;
}
else
{
printf("[%f %c %f] calc fail!n", numA, operate, numB);
}
return 0;
}
版本三的運行效果演示如下:
版本三中,各個類之間的關系如下圖所示:
-
-
- 運算類是一個抽象類(類名用斜體表示),具有兩個float類型的
公有
-
-
-
- 的(共有用**+號**)成員變量numA和numB以及一個GetResult公有方法四個計算類繼承(
繼承
-
-
-
- 用
空心三角+實線
-
-
-
- 表示)運算類,并實現(xiàn)對應的GetResult方法簡單工廠類依賴于(
依賴
-
-
-
- 用
箭頭+虛線
-
- 表示)運算類,通過createOperation方法實現(xiàn)運算類的實例化
3 總結
本篇主要介紹設計模式中的簡單工廠模式,首先通過一個活字印刷的小故事來體會程序設計中的可維護、可復用、可擴展、靈活性的思想,并引入面向對象設計模式中的三大基本思想:封裝、繼承、多態(tài),然后通過一個計算器的代碼實現(xiàn)的例子,通過C++實現(xiàn)了三個版本的代碼,由淺到深地理解面向對象的設計思想以及簡單工廠模式的使用。