工厂方法模式
先看问题:
业务场景:需要实现一个复杂的计算器,计算器包含3个菜单:标准计算器模式,科学计算器模式,程序员计算器模式。
其中标准计算器模式能够进行: 加法,减法,乘法运算
科学计算器模式能够进行: 根号,对数,幂运算
程序员计算器能够进行: 左移 ,右移,位与运算
其他要求:需要有用户界面
分析问题:
第一:需要支持模式操作
第二:根据不同的模式,需要3个操作符按钮需要能够进行不同的运算
首先,按照正常的代码来写:我们一般会先判断当前选择的模式,再根据不同的模式给操作符1,操作符2,操作符3赋予不同的功能:
代码如下:
public class OperationUtils {
//总方法,根据传入的mode选择具体的方法
public static double getResult(String mode, String opera, double op1, double op2) {
switch (mode) {
case "标准":
return getStandardResult(opera, op1, op2);
case "科学计算器":
return getScientificResult(opera, op1, op2);
case "程序员":
return getComputerResult(opera, op1, op2);
default:
throw new UnsupportedOperationException("不支持的模式");
}
}
//标准计算器方法,可以执行+ - *
private static double getStandardResult(String opera, double op1, double op2) {
switch (opera) {
case "+":
return op1 + op2;
case "-":
return op1 - op2;
case "*":
return op1 * op2;
default:
throw new UnsupportedOperationException("不支持的操作符");
}
}
//科学计算器方法,可以执行对数,开方,幂运算
private static double getScientificResult(String opera, double op1, double op2) {
switch (opera) {
case "√":
return Math.pow(op1, 1 / op2);
case "^":
return Math.pow(op1, op2);
case "log":
return Math.log(op1) / Math.log(op2);
default:
throw new UnsupportedOperationException("不支持的操作符");
}
}
//计算机计算器方法,可以执行左移,右移,与等位运算
private static double getComputerResult(String opera, double op1, double op2) {
int intOp1 = (int) op1;
int intOp2 = (int) op2;
switch (opera) {
case "<<":
return intOp1 << intOp2;
case ">>":
return intOp1 >> intOp2;
case "&":
return intOp1 & intOp2;
default:
throw new UnsupportedOperationException("不支持的操作符");
}
}
}
调用方法就很简单:
public class Main {
public static void main(String[] args) {
double op1=7;
double op2=9;
String model="程序员";
OperationUtils.getResult(model,"<<",op1,op2);
}
}
其中model
可以在用户点击标准模式
,科学模式
和程序员模式
的时候进行赋值。op1
和op2
可以根据用户点击的具体数字进行赋值。
首先,上面的代码是典型的面向过程代码,同理,如同在介绍简单工厂设计模式中提到的一样,计算器类作为核心逻辑,所有的逻辑都被暴露在外面,容易引起bug。
接下来根据设计模式原则分析一下上面的代码的问题:
SRP
:上面的代码中,当需要增加一个计算模式的时候,需要改变OperationUtils
,当某个计算模式中需要具体的增加某个方法的时候,也需要改变OperationUtils
,这违背的单一职责原则。OCP
:同样,当需要扩展/修改某个计算模式/计算符的时候,需要修改OperationUtils
对于其他的原则,由于这不是一个面向对象的类,所以不存在其他原则。
上面代码的唯一优点只在于:每个方法之间的分层比较清晰。
我们似乎发现问题和简单工厂比较像?
于是我们同样使用上篇文章学到的简单工厂模式重构上面的代码:
首先需要将核心业务使用面向对象包装起来:
定义一个Operation
类
public abstract class Operation {
//操作数1
protected double op1;
//操作数2
protected double op2;
//给操作数1赋值
public void setOp1(double op1){
this.op1=op1;
}
//给操作数2赋值
public void setOp2(double op2) {
this.op2 = op2;
}
//虚方法,由子类决定具体的操作
public abstract double getResult();
}
接下来为每个具体的运算符实现具体的操作:
//加法
public class AddOperation extends Operation {
@Override
public double getResult() {
return op1+op2;
}
}
//乘法
public class MulOperation extends Operation {
@Override
public double getResult() {
return op1*op2;
}
}
//...
//...
实现了9个操作后,我们使用OperaFactory
将他们包装起来:
public class OperationFactory {
//获取操作对象
public static Operation getOperation(String model, String opera, double op1, double op2) {
Operation operation;
switch (model) {
case "标准":
operation = getStandardResult(opera);
break;
case "科学计算器":
operation = getScientificResult(opera);
break;
case "程序员":
operation = getComputerResult(opera);
break;
default:
throw new UnsupportedOperationException("不支持的操作符");
}
//为对象赋值
operation.setOp1(op1);
operation.setOp2(op2);
return operation;
}
//标准计算器
private static Operation getStandardResult(String opera) {
switch (opera) {
case "+":
return new AddOperation();
case "-":
return new SubOperation();
case "*":
return new MulOperation();
default:
throw new UnsupportedOperationException("不支持的操作符");
}
}
//科学计算器
private static Operation getScientificResult(String opera) {
switch (opera) {
case "√":
return new RootOperation();
case "^":
return new PowerOperation();
case "log":
return new LogarithmOperation();
default:
throw new UnsupportedOperationException("不支持的操作符");
}
}
//计算机计算器
private static Operation getComputerResult(String opera) {
switch (opera) {
case "<<":
return new MoveLeftOperation();
case ">>":
return new MoveRightOperation();
case "&":
return new AndOperation();
default:
throw new UnsupportedOperationException("不支持的操作符");
}
}
}
使用方法如下:
public class Main {
public static void main(String[] args) {
double op1 = 7;
double op2 = 9;
String model = "程序员";
Operation operation=OperationFactory.getOperation(model,"<<",op1,op2);
operation.getResult();
}
}
同样op1
和op2
以及model
可以在用户触发的Click
事件的时候赋值。
继续分析上面的代码:
- 已经符合面向对象编程,将核心功能使用对象包装起来
SRP
:依然违背单一职责原则,不论是扩展计算模式,还是扩展运算符,都需要改变OperationFactory
,也就是说OperationFactory
担任的职责过多OCP
:同SRP
,扩展功能的时候需要修改OperationFactory
也就是说上面的代码,除了是将核心功能使用面向对象封装起来后,其他的问题依然存在。
第三次修改:
进过仔细分析
OperationFactory
我们可以发现,最主要的问题在于OperationFactory
需要进行两次switch
判断,第一次判断计算器模式,第二次判断用户点击的操作符 ,这样便导致了OperationFactory
一直违背SRP
,OCP
,因此我们可以将两次switch
判断分离开来、
发现问题后,我们再次重构:
//9个运算符类不变
//新建一个模式工厂父类类
public abstract class OperationFactory {
//统一管理获取到运算对象后的操作
//比如赋值,打日志等等
//但是我们并不知道获取的对象是什么,获取对象的方法作为抽象方法交由子类实现
public Operation getOperation(String opera, double op1, double op2) {
Operation operation = createOperation(opera);
operation.setOp1(op1);
operation.setOp2(op2);
return operation;
}
protected abstract Operation createOperation(String opera);
}
//每种模式自己实现自己的模式类
//标准计算器
public class StandardOperFactory extends OperationFactory {
@Override
public Operation createOperation(String opera) {
switch (opera) {
case "-":
return new SubOperation();
case "*":
return new MulOperation();
case "+":
return new AddOperation();
default:
throw new UnsupportedOperationException("不支持的操作符");
}
}
}
//科学计算器
public class ScientificOperFactory extends OperationFactory {
@Override
public Operation createOperation(String opera) {
switch (opera) {
case "^":
return new PowerOperation();
case "log":
return new LogarithmOperation();
case "√":
return new RootOperation();
default:
throw new UnsupportedOperationException("不支持的操作符");
}
}
}
//计算机计算器
public class ComputerOperFactory extends OperationFactory {
@Override
public Operation createOperation(String opera) {
switch (opera) {
case "<<":
return new MoveLeftOperation();
case "&":
return new AndOperation();
case ">>":
return new MoveRightOperation();
default:
throw new UnsupportedOperationException("不支持的操作符");
}
}
}
使用方式如下:
public class Main {
public static void main(String[] args) {
double op1 = 7;
double op2 = 9;
//具体的模式对象factory可以在用户触发`Click`事件的时候赋值/切换
OperationFactory factory =new StandardOperFactory();
Operation operation=factory.getOperation("+",op1,op2);
System.out.println(operation.getResult());
}
}
继续分析上面的代码:
- 符合面向对象
SRP
:没有了具体OperationFactory
类,而由每个模式自己管理自己的符号类,当需要增加某个模式下的某个符号时,只需要修改对应的模式即可,当需要增加计算模式的时候,完全不用修改源代码。OCP
:只符合一般的OCP
,当需要增加新的计算模式的时候,不用修改源代码。当需要增加某个计算模式下的某个符号的时候,需要修改对应的计算模式。OperationFactory
作为总的控制中心,可以统一管理产生的对象的生命周期以及产生对象前后的方法调用。
以上示例代码均在:https://github.com/dengchengchao/design-pattern
总结上面的代码,我们可以归纳出一种新的设计模式:工厂方法模式
工厂方法模式
定义
定义一个创建对象的接口,但让实现这个接口的类来决定实例化哪个类。工厂方法让类的实例化推迟到子类中进行,它也属于类创建型模式
工厂方法模式解决了什么问题?
简单说,工厂方法模式也是属于类创建模式,因此它解决的是创建类的时候所带来的问题。
- 当类的行为依赖于用户的多次输入而定的时候,我们可以通过工厂方法模式分离出每次选择
- 当类在被实例化之前或之后需要执行某些特定的操作的时候,我们可以通过工厂方法管理,并执行方法,可以规范化流程
- 当类的选择比较复杂的时候,我们可以通过工厂方法解耦,而用户不需要关心所产生的具体的类
UML
优点
- 工厂方法模式是对简单工厂的进一步扩展,从总体上来看,比较像是“使用简单工厂将简单工厂封装起来“,它同样可以将类的创建和使用的职责分离开来
- 工厂方法模式比简单工厂更加符合
OCP
,当新创建一个大类型的时候,可以完全不用修改原本的代码 - 工厂方法定义了创建对象的接口,并让子类去决定具体需要创建的对象,当时工厂方法作为获取所有类的控制类,可以统一管理所产生的对象
- 工厂方法隐藏了创建对象的具体细节,让用户可以更加关心所使用的功能
工厂方法隐藏了创建对象的具体细节,并且可以统一控制所有的对象,这是一个很重要的优点
比如在生产汽车的时候,我们需要根据用户的选择实例化车的材料,但是获取材料后需要将材料组装后才能返回给用户
再比如:在用户获取日志对象的时候,有时候可能在获取对象之前需要连接数据库等操作,这些操作都可以放在工厂类中实现,而对用户来说都是透明的
缺点
- 有些时候再扩展具体的产品的时候,依然会违背OCP
- 产生过多的类,还增加了代码量
- 增加了系统的复杂度
尊重劳动成果,转载注明出处
参考文章:
From-No-Factory-to-Factory-Method
《大话设计模式》
《Head First 设计模式》