先来考虑一道题目:
请用C++、Java、C#或VB.NET任意一种面向对象语言实现一个计算器控制台程序,要求输入两个数和运算符号,得到结果。
题目很简单,我们脑海里已经有了初步的思路和答案,写出来可能是这样的:
class Program
{
static void Main(string[] args)
{
try
{
Console.Write("请输入数字A:");
string strNumberA = Console.ReadLine();
Console.Write("请选择运算符号(+、-、*、/):");
string strOperate = Console.ReadLine();
Console.Write("请输入数字B:");
string strNumberB = Console.ReadLine();
string strResult = "";
switch (strOperate)
{
case "+":
strResult = Convert.ToString
(Convert.ToDouble(strNumberA) + Convert.ToDouble(strNumberB));
break;
case "-":
strResult = Convert.ToString
(Convert.ToDouble(strNumberA) - Convert.ToDouble(strNumberB));
break;
case "*":
strResult = Convert.ToString
(Convert.ToDouble(strNumberA) * Convert.ToDouble(strNumberB));
break;
case "/":
if (strNumberB != "0")
strResult = Convert.ToString
(Convert.ToDouble(strNumberA) / Convert.ToDouble(strNumberB));
else
strResult = "除数不能为0";
break;
}
Console.WriteLine("结果是:" + strResult);
Console.ReadLine();
}
catch (Exception ex)
{
Console.WriteLine("您的输入有错:" + ex.Message);
}
}
}
思路很清晰,依次读入数字和运算符号,再判断符号类别进行运算即可。可是,这样的代码满足要求吗?
再读一遍题目,原来我们漏掉了关键的部分——面向对象。
之前的代码对于题目本身来说并没有错,但这样的思维却使得我们的程序只为满足实现当前的需求,程序不容易维护,不容易扩展,更不容易复用。
举例来说,就像活字印刷术与刻板印刷术的区别:原来的刻版印刷术在改字的时候,必须要整个刻板全部重新刻,工作量极大,效率很低;而活字印刷术发明后,第一,要改,只需更改要改之字,此为可维护;第二,这些字并非用完这次就无用,完全可以在后来的印刷中重复使用,此乃可复用;第三,若要加字,只需另刻字加入即可,此是灵活性好。
学习了面向对象的分析设计编程思想后,我们要考虑通过封装、继承、多态把程序的耦合度降低,传统印刷术的问题就在于所有的字都刻在同一版面上造成耦合度太高所致;考虑用设计模式使得程序更加的灵活,容易修改,并且易于复用。
为了达到这个目的,应该让业务逻辑与界面逻辑分开,让它们之间的耦合度下降。只有分离开,才可以达到容易维护或扩展。
Operation运算类:
public class Operation
{
public static double GetResult(double numberA, double numberB, string operate)
{
double result = 0d;
switch (operate)
{
case "+":
result = numberA + numberB;
break;
case "-":
result = numberA - numberB;
break;
case "*":
result = numberA * numberB;
break;
case "/":
result = numberA / numberB;
break;
}
return result;
}
}
客户端代码:
class Program
{
static void Main(string[] args)
{
try
{
Console.Write("请输入数字A:");
string strNumberA = Console.ReadLine();
Console.Write("请选择运算符号(+、-、*、/):");
string strOperate = Console.ReadLine();
Console.Write("请输入数字B:");
string strNumberB = Console.ReadLine();
string strResult = "";
strResult = Convert.ToString(
Operation.GetResult(Convert.ToDouble(strNumberA),
Convert.ToDouble(strNumberB), strOperate));
Console.WriteLine("结果是:" + strResult);
Console.ReadLine();
}
catch (Exception ex)
{
Console.WriteLine("您的输入有错:" + ex.Message);
}
}
}
这样的话,在别的地方(Windows程序/Web版程序/其他软件)也可以复用Operation类了。
到这里,我们完成了面向对象中的封装。
现在考虑,如果我们希望增加一个开根(sqrt)运算,如何改?
你可能会想,直接在Operation类的switch中加一个分支就行了,问题是,让你加一个平方根运算,却需要让加减乘除的运算都得来参与编译,这合适吗?
很显然,我们应该把加减乘除等运算分离,修改其中一个不应影响另外的几个,增加运算算法也不应影响其他代码。
这就需要用到继承和多态。
Operation运算类:
public class Operation{
private double _numberA=0;
private double _numberB=0;
public double NumberA{
get{ return _numberA; }
set{ _numberA=value; }
}
public double NumberB{
get{ return _numberB; }
set{ _numberB=value; }
}
public virtual double GetResult(){
double result=0;
return result;
}
}
加减乘除类:
class OperationAdd : Operation{ // 加法类,继承运算类
public override double GetResult(){
double result =0;
result=NumberA + NumberB;
return result;
}
}
class OperationSub : Operation{ // 减法类,继承运算类
public override double GetResult(){
double result =0;
result=NumberA - NumberB;
return result;
}
}
class OperationMul : Operation{ // 乘法类,继承运算类
public override double GetResult(){
double result =0;
result=NumberA * NumberB;
return result;
}
}
class OperationDiv : Operation{ // 除法类,继承运算类
public override double GetResult(){
double result =0;
if(NumberB==0)
throw new Exception("除数不能为0.");
result=NumberA / NumberB;
return result;
}
}
写好代码之后,又有一个问题:如何让计算器知道我希望用哪一个算法呢?也就是如何去实例化一个对象呢?
到底要实例化谁,将来会不会增加实例化的对象(比如增加开根运算),这是很容易变化的地方,应该考虑用一个单独的类来做这个创造实例的过程,这就是工厂。也即我们的标题——简单工厂模式。
简单运算工厂类:
public class OperationFactory{
public static Operation createOperate(string operate){
Operation oper =null;
switch (operate){
case "+":
oper = new OperationAdd();
break;
case "-":
oper = new OperationSub();
break;
case "*":
oper = new OperationMul();
break;
case "/":
oper = new OperationDiv();
break;
}
return oper;
}
}
这样,只需要输入运算符号,工厂就实例化出合适的对象,通过多态,返回父类的方式实现了计算器的结果。
Operation oper;
oper = OperationFactory.createOperate("+");
oper.NumberA=1;
oper.NumberB=2;
double result =oper.GetResult();
不管是控制台程序,Windows程序,Web程序,PDA或手机程序,都可以用这段代码来实现计算器的功能,当有一天我们需要更改加法运算,我们只需要改 OperationAdd 就可以了。当我们需要增加各种复杂运算,比如平方根,立方根,自然对数,正弦余弦等时,只要增加相应的运算子类,并在运算类工厂的switch中增加分支就可以了。
UML类图
本章完。
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------
本文是作者阅读《大话设计模式》后,根据书中内容总结出的个人感悟。在此感谢程杰先生及其著作,本人受益匪浅。
本文是连载文章,此为第一章,在了解面向对象的基本概念后,学习简单工厂模式。
序章:https://blog.csdn.net/qq_36770641/article/details/82691403
下一章:https://blog.csdn.net/qq_36770641/article/details/82714288
(本文遵循《大话设计模式》中的讲述顺序和方法,代码部分使用C#)
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------