08.工厂方法模式(Factory Method Pattern)

8.1 再现活雷锋

  • 时间: 3月19日22点     地点: 小菜大鸟所住的客厅     人物: 小菜, 大鸟

    小菜来找大鸟, 说: “今天我们见到活雷锋了.”

    “哦, “ 大鸟感兴趣到, “现在已经很少提这个人名了, 说说看.”
    . . . . . . 以下省略500字.

8.2 简单工厂模式实现

    “好了, 我来是为了请教你一个问题, 这几天一直在研究工厂方法模式, 但是还是不太理解他和简单工厂的区别, 感觉还不如简单党工厂方便, 为什么要用这个模式, 到底这个模式的精髓在哪里?”
    “哈, 你刚才讲的故事不就是最好的工厂方法的样例嘛?”
    “薛磊峰的事? 怎么讲?”
    “那你先把简单工厂模式和工厂方法模式的典型实现说给我听听.”
    “哦, 首先简单工厂模式, 若以我写的计算器为例, 结构图如下.”



“工厂方法是这样写的.”

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class OperationFactory {
public static Operation createOperation(String operate){
Operation oper = null;
switch (operate) {
case "+":
oper = new OperationAdd();
break;
case "-":
oper = new OperationSub();
break;
case "*":
oper = new OperationMulti();
break;
case "/":
oper = new OperationDiv();
break;
}
return oper;
}
}

     “客户端的应用.”

1
2
3
4
5
6
7
8
public static void main(String[] args) {
Operation oper;
oper = OperationFactory.createOperation("+");
oper.setNumberA(1);
oper.setNumberB(2);
double result = oper.getResult();
System.out.println(result);
}

8.3 工厂方法模式实现

    ”那么如果是换成工厂方法模式来写这个计算器, 你能写吗?”
    ”当然是可以, 就是因为我写出来了, 才感觉好像工厂方法没什么好处!”
    ”计算器工厂方法模式的结构图是这样的.”



    ”先构建一个工厂接口. “

1
2
3
public interface Ifactory {
Operation createOperation();
}

    ”然后加减乘除各建一个具体工厂去实现这个接口.”

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
class AddFactory implements Ifactory{
@Override
public Operation createOperation() {
return new OperationAdd();
}
}
class SubFactory implements Ifactory{
@Override
public Operation createOperation() {
return new OperationSub();
}
}
class MultiFactory implements Ifactory{
@Override
public Operation createOperation() {
return new OperationMulti();
}
}
class DivFactory implements Ifactory{
@Override
public Operation createOperation() {
return new OperationDiv();
}
}

    ”对呀, 写的很好.” 大鸟说, “工厂方法模式就是这样写的, 你有什么问题?”

8.4 简单工厂 vs. 工厂方法

    ”怪就怪在这里呀, 以前我们不是说过, 如果我现在需要增加其他运算, 比如说 ‘M数的N次方’ 功能类, 然后去更改工厂方法, 当中加 ‘case’ 语句来判断, 现在用了工厂方法, 加功能类没有问题, 再加相关的工厂类, 也没问题, 但我要再去更改客户端, 这不等于不但没有简化难度, 反而增加了很多类和方法, 把复杂性增加了吗? 为什么要这样?”
    ”问得好, 其实这就是工厂方法模式和简单工厂的区别所在. 简单工厂模式的最大优点在于工厂类中包含了必要的逻辑判断, 根据客户端的选择条件动态的实例化相关的类, 对于客户端来说, 除去了与具体产品的依赖. 就像你的计算器, 让客户端不用管该用哪个类的实例, 只需要把 ‘+’ 给工厂, 工厂自动就给出了相应的实例, 客户端只要去做运算就可以了, 不同的实例会实现不同的运算. 但问题也就在这里, 如你所说, 如果要加一个 ‘M数的N次方’ 的功能, 我们是一定需要给运算工厂类的方法里加 ‘case’ 的分支条件的, 修改原有类? 这可不是好办法, 就等于说, 我们不等对扩展开放了, 对修改也开放了, 这样就违背了, 开放封闭原则.”
     “对, 于是工厂方法就来了”

工厂方法模式(Factory Method), 定义一个用于创建对象的接口, 让子类决定实例化哪一个类. 工厂方法使一个类的实例化延迟到其子类.



    ”我们讲过, 既然这个工厂类与分支耦合, 那么我就对他下手, 根据依赖倒转原则, 我们把工厂类抽象出一个接口, 这个接口只有一个方法. 然后, 所有要生产的具体类的工厂, 就去实现这个接口, 这样, 一个简单工厂模式的工厂类, 变成一个工厂抽象接口和多个具体生成的对象的工厂, 于是我们要增加 ‘求M数的N次方’ 的功能室, 就不需要更改原有的工厂类了, 只需要增加此功能运算类和相应的工厂类就可以了.”

    ”这样整个工厂和产品体系其实都么有修改的变化, 而只是扩展的变化, 这就完全符合了开放-封闭原则的精神. “
    ”哦, 工厂方法从这个角度讲, 的确要比简单工厂模式来得强. “
     “其实你仔细观察就会发现, 工厂方法模式实现时, 客户端需要决定实例化哪一个工厂来实现运算类, 选择判断问题还是存在的, 也就是说, 工厂方法把简单工厂的内部逻辑判断移动到了客户端代码来进行. 你想要加功能, 本来是改工厂类的, 而现在是修改客户端!
     “这也是我困惑的地方.”

8.5 雷锋工厂

    ”这个我们过会再讲, 以你刚才讲的故事来说吧, 你们班这位同学以雷锋的名义做好事, 而你们现在需要去代替他做好事, 其实这就是典型的工厂方法模式应用了. “
     “哦, 说来听听.”
     “首先, 薛雷峰作为一个大学生, 以雷锋的名义去帮助老人, 这里如何设计?”
    ”我的想法是这样的: 雷锋类, 拥有扫地, 洗衣, 买米等方法.”

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 雷锋
public class LeiFeng {
public void sweep(){
System.out.println("扫地");
}
public void wash() {
System.out.println("洗衣服");
}
public void bugRice(){
System.out.println("买米");
}
}

    ”‘学雷锋的大学生’类, 继承 ‘雷锋’”

1
2
3
4
// 学雷锋的大学生
public class Undergraduate extends LeiFeng {
}

    ”然后客户端代码.”

1
2
3
4
5
6
7
public static void main(String[] args) {
LeiFeng xueleifeng = new Undergraduate();
xueleifeng.bugRice();
xueleifeng.sweep();
xueleifeng.wash();
}

    ”小菜写的不错,” 大鸟说, “现在假设你们有三个人要去代替他做这些事, 那应该怎么写?”
     “那就应该实例化三个 ‘学雷锋的大学生’ 对象了.” 小菜说着同事写出了代码.

1
2
3
4
5
6
LeiFeng student1 = new Undergraduate();
student1.bugRice();
LeiFeng student2 = new Undergraduate();
student2.sweep();
LeiFeng student3 = new Undergraduate();
student3.wash();

    ”你们都是要毕业的, 而帮助老人却是长期的工作.所以 ‘社区志愿者’ 更合适, 此是这样的写法就非常不合适了, 因为我们需要更改多个实例化的地方.”
    ”是呀, 其实老人不需要知道谁来做好事, 他只需要知道是学雷锋的人来帮忙就可以了. 所以还需要增加一个继承 ‘雷锋’ 类的 ‘社区志愿者’ 类”

1
2
3
4
// 社区志愿者
public class Volunteer extends LeiFeng{
}

“再写简单工厂类.”

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class SimpleFactory {
public static LeiFeng createLeiFeng(String type){
LeiFeng result = null;
switch (type) {
case "学雷锋的大学生":
result = new Undergraduate();
break;
case "社区志愿者":
result = new Volunteer();
break;
}
return result;
}
}

    ”客户端的代码, 如果要换, 就只需要 ‘学雷锋的大学生’, 为 ‘社区志愿者’”

1
2
3
4
5
6
7
8
public static void main(String[] args) {
LeiFeng studentA = SimpleFactory.createLeiFeng("学雷锋的大学生");
studentA.bugRice();
LeiFeng studentB = SimpleFactory.createLeiFeng("学雷锋的大学生");
studentB.sweep();
LeiFeng studentC = SimpleFactory.createLeiFeng("学雷锋的大学生");
studentC.wash();
}

     “好, 此是你就发现了, 你需要在任何实例化的时候写出这个工厂的代码. 这里有重复, 也就有了坏的味道. 你在用工厂方法模式写一遍.”
     “好的, 那就需要雷锋工厂了. “

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 雷锋工厂
public interface IFactory {
LeiFeng createLeiFeng();
}
// 学雷锋的大学生工厂
public class UndergraduateFactory implements IFactory {
@Override
public LeiFeng createLeiFeng() {
return new Undergraduate();
}
}
// 社区志愿者工厂
public class VolunteerFactory implements IFactory{
@Override
public LeiFeng createLeiFeng() {
return new Volunteer();
}
}

     “客户端的调用只需要这样就可以了.”

1
2
3
4
5
6
IFactory factory = new UndergraduateFactory(); // 要换成 '社区志愿者' 修改这里就可以了
LeiFeng student = factory.createLeiFeng();
student.bugRice();
student.sweep();
student.wash();

     “我明白了, 尽管如果要换成 ‘社区志愿者’ 也还是要求改代码, 但是只需要修改一处就可以了. 这是最佳做法.”
     “这是最佳做法? NO, NO, NO, 不是最好的, 不过应该是比较好的了. 你现在明白什么时候用简单工厂模式, 生么时候用工厂方法模式了吗?”
     “我感觉工厂方法克服了简单工厂违背开放封闭原则的缺点, 又保持了封装对象创建过程的优点.”
     “说得好, 他们都是集中封装了对象的创建, 是的要更换对象时, 不需要太大的改动就可以实现, 降低了客户端程序与产品对象的耦合. 工厂方法模式简单工厂模式的进一步的抽象和推广. 由于使用了多态性, 工厂方法模式保持了简单工厂模式的优点, 而且又克服了他的缺点 但缺点是由于没加一个产品, 就需要加一个产品工厂的类, 增加了额外的开发量.”
     “你说这还不是最佳做法? 那应该如何做呢? 还有就是这样还没有避免修改客户端的代码呀?”
     “哈, 之前我们就提到过, 利用 ‘反射’ 可以解决避免分支判断的问题. 不过今天还是不急, 等以后再谈. 你明天不是要去看望人家老人吗, 赶紧滚去睡吧.”

~感谢捧场,您的支持将鼓励我继续创作~