之前一段面试经常被问到设计模式的问题,而自己对于此也只是一知半解,就能写个单例什么的,但是具体使用场景以及理解可谓是一无所知,最近准备对设计模式进行一番探索,通过此系列博客进行记录。从同学处借了一本设计模式相关的书籍——《大话设计模式》,希望先通过此本书籍对于设计模式进行一个初步的学习了解,书中的代码都是通过.net中的c#语言实现的,自己也借此转化为java代码,希望能加深自己的理解及认识。此篇也是我第一次这么认真的来写,希望自己可以一直坚持下去养成好习惯,如果有什么不当的地方也希望可以予以指正。
闲话少叙,进入正题,首先是解决一道面试题,题目如下:
请用C++、Java、、C#或VB.NET任意一种面向对象语言实现一个计算器控制程序,要求输入两个数和运算符号,得到结果。
根据题意最先想到的便是如下的解决办法,也是最基本最普遍的实现方式:
import java.util.Scanner;
public class Calc {
private static void prt(String s) {
System.out.println(s);
}
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
prt("请输入第一个数字:");
Double num1 = sc.nextDouble();
prt("请输入第二个数字:");
Double num2 = sc.nextDouble();
prt("请输入运算符:");
String opration = sc.next();
switch (opration) {
case "+":
prt("result:"+(num1 + num2));
break;
case "-":
prt("result:"+(num1 - num2));
break;
case "*":
prt("result:"+(num1 * num2));
break;
case "/":
if (num2==0) {
prt("除数不能为0。");
}else {
prt("result:"+(num1 / num2));;
}
break;
default:
prt("运算符输入有误!");
break;
}
}
}
1、业务封装
public class Operation {
public static double getResult(double num1,double num2,String oprate){
double result = 0d;
switch (oprate) {
case "+":
result = num1 + num2;
break;
case "-":
result = num1 - num2;
break;
case "*":
result = num1 * num2;
break;
case "/":
result = num1 / num2;
break;
}
return result;
}
}
然后实现客户端调用:
import java.util.Scanner;
public class calc {
private static void prt(String s) {
System.out.println(s);
}
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
prt("请输入第一个数字:");
Double num1 = sc.nextDouble();
prt("请输入第二个数字:");
Double num2 = sc.nextDouble();
prt("请输入运算符:");
String oprate = sc.next();
if (num2==0&&oprate.equals("/")) {
prt("reslut:除数不能为0.");
}else {
prt("result:"+Operation.getResult(num1, num2, oprate));
}
}
}
通过上述步骤实现了业务逻辑的封装,是之前的计算方法更加易于复用,当需要相同的计算方法只需要调用我们所封装的Opration类的getResult方法即可,并且我们如果需要进行修改或增加计算方法的话,只需要对该方法进行修改即可,对于客户端调用的程序则不需要修改。但是这样同时会有另外的一些问题出现,如我们要对之前的方法进行扩展增加新的求开方的算法,这时我们需要修改Opration类增加新的分支操作,但是有可能改动时候不小心错改了其他计算方法而对其它计算方法造成影响,并且我们只是需要新增一个算法,却需要整个类都要修改,我们会发现我们的算法程序耦合度太高,在扩展及修改时会有很多不利,所以下面我们对改算法类进行改造,使其解耦。
2、解耦
首先编写一个算法的父类Opration:
public class Operation {
private double num1;
private double num2;
public double getNum1() {
return num1;
}
public void setNum1(double num1) {
this.num1 = num1;
}
public double getNum2() {
return num2;
}
public void setNum2(double num2) {
this.num2 = num2;
}
public double getResult() throws Exception {
double result = 0d;
return result;
}
}
然后将加减乘除拆分成四个不同的类继承Opration:
public class OperationAdd extends Operation {
@Override
public double getResult() {
return getNum1()+getNum2();
}
}
public class OperationSub extends Operation {
@Override
public double getResult() {
return getNum1()-getNum2();
}
}
public class OperationMul extends Operation {
@Override
public double getResult(){
return getNum1()*getNum2();
}
}
public class OperationDiv extends Operation {
@Override
public double getResult() throws Exception {
if (getNum2()==0) {
throw new Exception("除数不能为0.");
}else {
return getNum1()/getNum2();
}
}
}
通过上面的改造,我们的计算程序已经成功解耦,现在加减乘除算法都被拆分成不同的类,并且他们同时继承于同一个算法类,现在如果要修改其中的一个算法,就不需要关心其他的算法,如果要扩展我们的算法,只需要新增Opration的子类并且重写getResult()方法即可。
但是现在问题来了,我们如何让我们的计算器知道我们需要用到的是哪个算法呢?也就是我们在客户端调用时候具体实例化哪个对象,这时候我们就有必要考虑用一个单独的类来做这个创造实例的过程,也就是我们的工厂。
3、简单工厂模式
工厂类如下:
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;
}
}
通过上面的工厂类,我们只需要根据运算符号,工厂就可以实例化出适合的对象,然后通过多态返回父类的方式实现了计算结果,客户端代码如下:
public class calc {
private static void prt(String s) {
System.out.println(s);
}
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
System.out.println("请输入第一个数字:");
Double num1 = sc.nextDouble();
System.out.println("请输入第二个数字:");
Double num2 = sc.nextDouble();
System.out.println("请输入运算符:");
String oprate = sc.next();
Operation oper = OperationFactory.createOperate(oprate);
oper.setNum1(num1);
oper.setNum2(num2);
try {
prt("result:"+oper.getResult());
} catch (Exception e) {
e.printStackTrace();
}
}
}
至此,我们已经将我们最初始的计算器通过简单工厂模式改造完毕,我们会发现可能我们开始的方法看起来是最简单的,一个类全部都解决了,而我们通过设计模式改造之后反而好像更加复杂了,又创建出了各种不同的类,但是如果我们从长远去考虑,从面向对象的编程思想去思考问题,会发现后者其实是更利于一个程序的长期使用的,无论是在程序的复用、修改、扩展等方面都有着前者无法望其项背的优势。而这些也正是使用设计模式来设计程序的巨大优势,而我也感觉走进了一个新的世界一样,但这只是冰山一角,后面的路还有很长要走,希望自己能坚持走到尽头,而不是从入门到放弃。
关于简单工厂模式的一些定义什么我就不再去赘述,主要是感觉在学习这个过程中,通过代码一步步的改造,可以亲身体会到那种变化,有种化腐朽为神奇的感觉,可能编程真的是一门艺术吧。