工厂方法模式可以分为:简单(静态)工厂方法模式、工厂方法模式、抽象工厂方法模式。他们都属于创建型模式。
其中简单工厂方法模式不属于标准的23个设计模式,但是学习工厂方法模式的好引子。
而其工厂模式几乎是Android中用到的最广方的设计模式了,fwk层就充斥着这样的模式。
本篇就来学习前两种方法模式。
1. 简单工厂模式
简单工厂模式又叫静态工厂模式。
1.1 代码例子
假如我们现在使用面向对象的思想,来写一个计算器,我们会把运算和显示分开我们可能会这么写:
// 运算类:
public class Operation {
public double getResult(double a, double b, String operate) {
double result = 0d;
switch (operate) {
case "+":
result = a + b;
break;
case "-":
result = a - b;
break;
case "*":
result = a * b;
break;
case "/":
result = a / b;
break;
}
return result;
}
}
// 客户端类:
public class FactoryMain {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
double a = scanner.nextDouble();
double b = scanner.nextDouble();
String op = scanner.next();
double res = Operation.getResult(a, b, op);
System.out.println("结果为:" + res);
}
}
该方案做到封装,将算法封装了起来,别人不需要了解计算器实现,只需要给数字和操作符就能完成运算了。
如果我们想要加运算符(比如开根号或者开平方)或者修改运算符,只需要在 Operation.getResult()
的switch中加case就行了。
但是这样的做法,并不符合 开放-封闭原则
,即 getResult
方法作为一个出口方法,如果频繁的改动,会对原有的运行良好的功能代码产生了变化。
而根据我们之前对设计模式松耦合的理解,我们应该把每种运算给分离出来,并实现多态,让Operation变成想要变成的运算符,大致是这样的:
// 操作的父类
public abstract class Operation {
double a;
double b;
abstract double getResult();
}
//继承Operation的各个操作符类:
public class OperationAdd extends Operation {
@Override
double getResult() {
return a + b;
}
}
public class OperationSub extends Operation {
@Override
double getResult() {
return a - b;
}
}
public class OperationMul extends Operation {
@Override
double getResult() {
return a * b;
}
}
public class OperationDiv extends Operation {
@Override
double getResult() {
return a / b;
}
}
// 客户端类
public class FactoryMain {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
double a = scanner.nextDouble();
double b = scanner.nextDouble();
String op = scanner.next();
Operation operation;
switch (op) {
case "+":
operation = new OperationAdd(); // 父类引用指向子类对象
break;
case "-":
operation = new OperationSub();
break;
case "*":
operation = new OperationMul();
break;
case "/":
operation = new OperationDiv();
break;
default:
operation = new OperationAdd();
}
operation.setA(a);
operation.setB(b);
System.out.println(operation.getResult());
}
}
这样,我们将各个算法从Operation中分离出来,这样如果我们还想加方法的话就写一个类继承自Operation,然后实现其getResult()。
但是上述方法中,客户端类做了一个事情,那就是根据 operation
来创建不同的Operation类,这样的做法对客户端来说其实是多余的,客户端不需要知道我有什么算法。
简单工厂模式解决了客户端的问题,它定义了一个静态工厂类,客户端只需要把 operation交给这个工厂类,工厂类自己来产生对应的Operation
对象,就行了:
// 工厂类
public class OperationFactory {
public static Operation createOperation(String op) {
Operation operation;
switch (op) { // 实现了之前Main方法里面做的事情
case "+":
operation = new OperationAdd();
break;
case "-":
operation = new OperationSub();
break;
case "*":
operation = new OperationMul();
break;
case "/":
operation = new OperationDiv();
break;
default:
operation = new OperationAdd();
}
return operation;
}
}
// 客户端:
public class FactoryMain {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
double a = scanner.nextDouble();
double b = scanner.nextDouble();
String op = scanner.next();
Operation operation = OperationFactory.createOperation(op); // 1
operation.setA(a);
operation.setB(b);
System.out.println(operation.getResult());
}
}
注释1:这样Main方法就不需要Operation的具体算法,只需要传入op,就能得到op对应的运算对象。
我们来看看改进后的UML图:
上述例子非常好理解,而且我们平时也经常使用到类似的操作。(上图有问题,但是后面发现已经晚了…将就着看一下吧)
接下来,我们学习更加强大的工厂模式。
2.工厂模式
2.1 基于简单工厂模式的工厂模式实现
如果换成工厂模式来实现第一节中讲解的计算机,画风就有一些不同了。
在工厂模式中,会把原有的工厂类升华为抽象接口类,然后对应各个Operation实现类实现具体工厂类,看下图:
来看看代码实现:
... // 省略各个操作类和Operation抽象类
//工厂接口
public interface IFactory {
Operation createOperation();
}
// 工厂方法
public class AddFactory implements IFactory{
@Override
public Operation createOperation() {
return new OperationAdd(); // 返回真正的操作类
}
}
public class SubFactory implements IFactory {
@Override
public Operation createOperation() {
return new OperationSub();
}
}
... //省略剩下两个操作工厂
// 客户端方法:
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
double a = scanner.nextDouble();
double b = scanner.nextDouble();
String op = scanner.next();
IFactory operFactory;
switch (op) {
case "+":
operFactory = new AddFactory(); // 创建对应的工厂
break;
case "-":
operFactory = new SubFactory();
break;
case "*":
operFactory = new MulFactory();
break;
case "/":
operFactory = new DivFacotry();
break;
default:
operFactory = new AddFactory();
break;
}
Operation oper = operFactory.createOperation(); // 用对应的工厂来创建对应的操作类对象
oper.setA(a);
oper.setB(b);
System.out.println("结果为:" + oper.getResult());
}
可以看到,在简单工厂的基础上,工厂方法模式将工厂那个模块又抽象成了一个接口和多个实现接口的工厂。
在这个例子中,不仅加了许多的类,还把switch语句放在了客户端中,看起来工厂方法比简单工厂要复杂,这是为什么呢?没有必要才对呀。
2.2 简单工厂vs工厂方法
工厂方法模式和简单工厂的区别就在于:
简单工厂模式最大的优点在于工厂类中包含了必要的逻辑判断,根据客户端的选择条件动态实例化相关的类,对于客户端来说,去除了与具体产品的依赖。
但是对于简单工厂方法,如果要加一个“异或”运算,需要去工厂类 OperationFactory
中的switch加分支条件,修改原有的类。
这其实并不是好办法,因为它对修改开放了,这就不符合 开放-封闭原则
。所以工厂方法模式就出来了。
工厂方法模式(Facotry Method),定义一个用于创建对象的接口,让子类决定实例化哪一个类。工厂方法使一个类的实例化延迟到子类。
来看下工厂方法模式的UML图:
这样,在我们新增了功能后,就无须更改原有的工厂类,对修改关闭了。
工厂方法模式实现时,客户端需要决定实例化哪一个工厂来实现运算类,选择判断的问题还是存在的,也就是说,工厂方法把简单工厂的内部逻辑判断转移到了客户端代码来进行。你想要加功能,本来是改工厂类的,而现在是修改客户端。
2.3 另一个例子
该例子为《大话》第八章的例子,即学雷锋的例子。
首先,一个学生以学习雷锋的名义去帮助老人做事,这里我们用代码实现是这样的:
// 先创建雷锋的抽象类,表示学习雷锋做了什么事情:
public class LeiFeng {
public void sweep(){
System.out.println("扫地");
}
public void wash(){
System.out.println("洗衣服");
}
public void buy() {
System.out.println("买东西");
}
}
// 学习雷锋的学生肯定是继承自雷锋类的:
public class Undergraduate extends LeiFeng {
}
// 客户端实现:
public static void main(String[] args) {
LeiFeng student = new Undergraduate();
student.sweep();
student.wash();
student.buy();
}
这时假设有三个人要代替这个学生做,那么该怎么办?好说,创建三个学生对象:
// 客户端实现:
public static void main(String[] args) {
LeiFeng student1 = new Undergraduate();
student1.sweep();
LeiFeng student2 = new Undergraduate();
student2.wash();
LeiFeng student3 = new Undergraduate();
student3.buy();
}
但是学生都是要毕业的,而帮助老人却是长期工作,所以“社区志愿者”更合适,这样的写法就不合适了,因为我们需要更改多个实例化的地方。
所以这个时候我们需要一个工厂,来产生学生或者志愿者:
// 社区志愿者类:
public class Volunteer extends LeiFeng {
}
// 创建一个简单雷锋工厂
public class LeifengFactory {
public static LeiFeng createLeifeng(String type) {
LeiFeng lf = null;
switch (type) {
case "学习雷锋的学生":
lf = new Undergraduate();
break;
case "社区志愿者":
lf = new Volunteer();
break;
}
return lf;
}
}
// 客户端代码
public static void main(String[] args) {
LeiFeng studentA = LeifengFactory.createLeifeng("学习雷锋的学生");
studentA.sweep();
LeiFeng studentB = LeifengFactory.createLeifeng("学习雷锋的学生");
studentB.wash();
LeiFeng studentC = LeifengFactory.createLeifeng("学习雷锋的学生"); // 重复三行代码
studentC.buy();
}
到这个时候就会发现,我们需要在任何实例化的时候写出这个工厂的代码。这里有了重复,这就是坏味道。(即如果工厂关闭了,我们就创建不了对象了…)
这个时候,如果我们使用了工厂方法模式,来创建雷锋工厂,那就是这样的:
// 雷锋工厂接口类
public interface ILeifengFactory {
LeiFeng createLeifeng();
}
// 创建社区志愿者的工厂:
public class VolunteerFactory implements ILeifengFactory {
@Override
public LeiFeng createLeifeng() {
return new Volunteer();
}
}
// 创建学生的工厂
public class UndergraduateFactory implements ILeifengFactory {
@Override
public LeiFeng createLeifeng() {
return new Undergraduate();
}
}
// 客户端
public static void main(String[] args) {
ILeifengFactory factory = new UndergraduateFactory(); // 1: 创建学生工厂
LeiFeng student = factory.createLeifeng(); // 学生工厂可以实例化多个学生
student.sweep();
student.wash();
student.buy();
}
如果我们需要将学生换成志愿者,如果是简单工厂方法,我们需要修改每处工厂,这样如果我们之前实例化的地方很多,就会很耗时。
而如果是工厂方法模式,我只需要修改上述注释1中的代码,把创建学生工厂换成创建志愿者工厂。
2.4 总结
相比于简单工厂模式,工厂方法模式它克服了简单工厂模式违背的开放-封闭原则
,又保持了封装对象创建过程的优点。
但是它也有它的缺点,那就是在修改时,不可避免的去修改客户端的代码,而且每次增加一个产品,就要为该产品写一个工厂类,增加了额外的开发量~
但是这些缺点不是不能克服的,工厂方法还有它可以升级的地方。