假如有如下这样一道笔试题:
请用c++ 、Java、C#、或VB.NET 任意一种面向对象语言实现一个计算器控制台程序。要求:输入两个数和运算符号,得到结果。
或许一拿到题。心里就想:“这还不简单?获取控制台的输入参数,后根据if或者switch进行运算符判断。将结果输出。”
public static void main(String[] args) throws Exception {
Scanner sc = new Scanner(System.in);
System.out.println("输入第一个数字A");
double numA = sc.nextDouble();
System.out.println("输入运算符:+、-、*、/");
String operator = sc.next();
System.out.println("输入第二个数字B");
double numB = sc.nextDouble();
double result = 0d;
switch (operator) {
case "+":
result = numA + numB;
break;
case "-":
result = numA - numB;
break;
case "*":
result = numA * numB;
break;
case "/":
result = numA / numB;
break;
default:
break;
}
System.out.println(String.format("结果是:%s", result));
sc.close();
}
乍一看,似乎没什么问题。但是,会不会出现一些特殊情况呢?比如说,除数为0,运算符并非+-*/
呢?因此我们可以将程序做一些细微的调整来预防这些潜在的问题。
case "/":
if(0 == numB){
throw new Exception("除数不能为0!");
}
result = numA / numB;
break;
default:
throw new Exception("运算符错误!");
看起来潜在问题被解决掉了。但是回过头看题目
请用c++ 、Java、C#、或VB.NET 任意一种面向对象语言实现一个计算器控制台程序。要求:输入两个数和运算符号,得到结果。
如果只是这么简单,那你和他人区别在哪呢?现在让我们将代码写的更优秀一些。
解决问题之前,自然要先提出问题:假如现在需要增加一个开方运算。我们在上述基础上应该怎么做呢?
很容易想到在多加一个case分支就可以了。
好的,现在问题来了,你可以增加一个分支。但是这意味着,你需要拿到全部的源码,才可以进行添加。在添加的过程中,如果"不小心"碰到了别的位置的代码,可能会导致1+1=3
的情况产生喔。
因此,我们需要将这 “一整坨” 代码,拆分开来,也就是所谓的 "降耦"
我们可以通过如下方式来将程序耦合度降低。
class OperationAdd {
public double getResult(double numA, double numB) {
return numA + numB;
}
}
class OperationSub {
public double getResult(double numA, double numB) {
return numA - numB;
}
}
class OperationMul {
public double getResult(double numA, double numB) {
return numA * numB;
}
}
class OperationDiv {
public double getResult(double numA, double numB) throws Exception {
if (0 == numB) {
throw new Exception("除数不能为0");
} else {
return numA / numB;
}
}
}
这样我们就可以将上述switch更新成如下写法。
switch(operator){
case "+":
result = new OperationAdd().getResult(numA, numB);
break;
case "-":
result = new OperationSub().getResult(numA, numB);
break;
case "*":
result = new OperationMul().getResult(numA, numB);
break;
case "/":
result = new OperationDiv().getResult(numA, numB);
break;
default:
throw new Exception("运算符错误!");
}
这样做完之后,我们便可以在新增一种运算,或者修改某一运算时不会碰到其他代码。
假如有一千个,一万个,运算方式。并且我在别处想要使用这种计算方法时,那么多的类,我究竟应该new哪一种呢?找都要找半天。头大喔
因此,引申出的设计模式是:简单工厂模式
也就是说,到底要new谁,将来不论是否会增加运算,我都可以通过修改工厂
来完成对象的实例化。
上述例子中,不论哪种计算方式,均是计算的一种。我们可以将他们全部继承于一个基类,通过多态的方式,返回所需要的对象,并在程序执行期间,进行动态绑定,从而正确调用自己所需的方法。
因此我们建立一下几个类:
class Operation {
public double getResult(double numA, double numB) throws Exception {
return 0d;
}
}
class OperationAdd extends Operation {
public double getResult(double numA, double numB) {
return numA + numB;
}
}
class OperationSub extends Operation {
public double getResult(double numA, double numB) {
return numA - numB;
}
}
class OperationMul extends Operation {
public double getResult(double numA, double numB) {
return numA * numB;
}
}
class OperationDiv extends Operation {
public double getResult(double numA, double numB) throws Exception {
if (0d == numB) {
throw new Exception("除数不能为0");
} else {
return numA / numB;
}
}
}
class OperationFactory {
public static Operation getOperation(String operator) throws Exception {
Operation operation;
try {
switch (operator) {
case "+":
operation = new OperationAdd();
break;
case "-":
operation = new OperationSub();
break;
case "*":
operation = new OperationMul();
break;
case "/":
operation = new OperationDiv();
break;
default:
throw new Exception("运算符错误");
}
return operation;
} catch (Exception e) {
throw e;
}
}
}
这样的工厂建好之后,我们便可以通过OperationFactory.getOperation(String operator)
来获得需要的对象并通过调用其getResult方法,来获得正确的结果。
所以程序调整为:
public static void main(String[] args) throws Exception {
Scanner sc = new Scanner(System.in);
try {
System.out.println("输入第一个数字A");
double numA = sc.nextDouble();
System.out.println("输入运算符:+、-、*、/");
String operator = sc.next();
System.out.println("输入第二个数字B");
double numB = sc.nextDouble();
Operation operation = OperationFactory.getOperation(operator);//通过工厂获得对象
double result = operation.getResult(numA, numB);//动态绑定来获得正确的计算结果
System.out.println(String.format("结果是:%s", result));
} catch (Exception e) {
throw e;
} finally {
sc.close();
}
}
回忆一下,Java的三大特性是什么?
张口就来系列:继承
、封装
、多态
在这个简单的计算器程序中。我们通过将计算子类继承于计算基类,并通过多态来返回父类的方式实现了计算结果。
这样实现的代码,如果某一天,需要修改加法运算方式,我们只需修改OperationAdd就可以了。如果需要增加各种复杂运算,如平方,立方根,对数,sin,cos等等等等。我们就可以通过添加相应的子类,并在工厂中的switch内添加分支就可以。
这也就是所谓的简单工厂模式
相比最开始的代码,显然工厂模式下,更易扩展、复用、维护。