小光热干面提供饮料了---简单工厂

前情提要

本意是想像美剧的previously那样, 不知道怎么翻译好, 求翻译达人赐教…

上集讲到, 小光辞了工作, 开起了热干面的店子, 用Builder模式改造了热干面的构建过程, 是日渐稳定有效起来, 生意也是越来越好.

但是小光是善于观察的同学啊, 他发现热干面真的好干啊(好像一般人也都能发现, 鬼脸~). 心想, 解决用户痛点才产品的存在根本啊, 是时候推出新东西了.

于是他决定跟推出自己的光氏饮料产品.

饮料的制作

经过一番调查和走访, 小光发现几个特点:

  • 大家对这个饮料要求不高(码农的屌丝特性啊) , 解渴为主.
  • 因为赶着上班, 一般要求要快, 最好可以直接拿走.
  • 品种要丰富, 大家口味不一啊.

于是, 小光选用了最新的XX牌 (有广告商找我吗? 哈哈.) 速溶饮料. 制作饮料的过程很简单, 很快, 小光做出了第一杯饮料—橙汁:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class OrangeJuice {
public void make() {
// 1. 拿出一次性饮料杯
System.out.println("拿出一次性饮料杯");
// 2. 加入速溶橙汁粉
System.out.println("加入速溶橙汁粉");
// 3. 加水冲兑
System.out.println("加水");
// 4. 加盖, 打包
System.out.println("加盖, 打包");
}
}

小光的思考

看起来, 程序似乎是可以运转了, 让我们投入生产吧…
然而, 小光何许人啊, 毕竟是在码农界混了好些年的同志啊. 还没有投入使用, 就发现了”问题”:

饮料有很多种, 过程都类似, 只是放的速溶包不一样. 如果我每个都这么写, 改天真有人找我打广告, 杯子上印个二维码什么的, 我不是每个饮料的制作过程都要改啊

灵光一现, 小光想起了面向对象思想中提到的抽象, 封装, 于是乎, 小光改写了自己的程序:

他抽象出来一个”Drink”类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public abstract class Drink {
public void make() {
// 1. 拿出一次性饮料杯
System.out.println("拿出一次性饮料杯");
// 2. 加入速溶橙汁粉
System.out.println("加入" + getInstantPackage());
// 3. 加水冲兑
System.out.println("加水");
// 4. 加盖, 打包
System.out.println("加盖, 打包");
}
abstract String getInstantPackage();
}

可乐, 酸梅汤, 橙汁皆继承了Drink:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class Coke extends Drink {
@Override
String getInstantPackage() {
return "速溶可乐粉";
}
}
public class PlumJuice extends Drink {
@Override
String getInstantPackage() {
return "速溶酸梅粉";
}
}
public class OrangeJuice extends Drink {
@Override
String getInstantPackage() {
return "速溶橙汁粉";
}
}

开卖

制作完饮料, 准备开卖了, 是这样的:

1
2
3
4
5
6
7
8
public class XiaoGuang {
public static void main(String[] args) {
OrangeJuice orangeJuice = new OrangeJuice();
orangeJuice.make();
}
}

第二天, 生意实在太好, 小光请了表妹临时来帮忙泡制饮料. 而且, 小光还发现自己也无需关注用户要什么饮料了, 直接让用户告诉表妹, 表妹直接制作饮料给用户就行~

表妹负责根据用户要求生产饮料:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class Cousins {
public static Drink create(String drinkType) {
// Java7开始, switch支持String
switch (drinkType) {
case "橙汁":
return new OrangeJuice();
case "酸梅汤":
return new PlumJuice();
case "可乐":
return new Coke();
default:
return new OrangeJuice();
}
}
}

小光不再关注饮料具体种类:

1
2
3
4
5
6
7
8
public class XiaoGuang {
public static void main(String[] args) {
Drink drink = Cousins.create("可乐");
drink.make();
}
}

加上饮料套餐之后, 生意果然越来越好, 估计也有美女助阵的加成, 哈哈…

故事之后

可能有些同学已经看出一点熟悉感了, 我们把这些关系一个UML表示下:

如此, 应该就比较清晰了, 这个就是我们今天的主题—简单工厂.

严格来说, 简单工厂并非一种设计模式, 可以说是一种编码习惯.

那么我们为什么要使用简单工厂, 或者说这么做有什么好处呢?

从我们这个例子中, 可以看到, 小光(Client类)不需要去关注饮料泡制(Drink make)这个过程了, 有更多的时间去接待客户, 只需从表妹(工厂类)那拿到相应的饮料给客户即可.

这个其实是蕴含了单一职责的编程思想:

  • 小光的职责是接待客户
  • 表妹的职责是泡制饮料

简单工厂一般来说, 使用一个静态方法来生产产品, 故而有时也称之为静态工厂方法模式.

扩展阅读

在这个故事的过程中, 我们提到了面向对象的抽象, 封装特性. 实际上我们所有的OOD原则, 设计模式, 都是基于面向对象的思想的, 离不开抽象, 封装, 继承, 多态几大特性. 随着对这些设计模式的分析, 也可以让我们加深面向对象的编程思想.

以Glide为例, 其中的DefaultConnectivityMonitorFactory.java就是一个类似的简单工厂实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class DefaultConnectivityMonitorFactory implements ConnectivityMonitorFactory {
@NonNull
public ConnectivityMonitor build(
@NonNull Context context,
@NonNull ConnectivityMonitor.ConnectivityListener listener) {
final int res = context.checkCallingOrSelfPermission("android.permission.ACCESS_NETWORK_STATE");
final boolean hasPermission = res == PackageManager.PERMISSION_GRANTED;
if (hasPermission) {
return new DefaultConnectivityMonitor(context, listener);
} else {
return new NullConnectivityMonitor();
}
}
}

与表妹Cousins(工厂)的实现很类似, 只是这个Factory具体创建什么产品不是由传入的参数决定的, 是在内部的逻辑决定的.

转化为类图关系, 更加清晰:

一直在说的话: 所谓架构, 设计模式都是一种思想, 没有固定的招式, 所有的这些招式都是让我们入门, 了解面向对象的基础思想, 然后能运用无形. 共勉.

留个尾子

大家可能有发现, 简单工厂的弊端也是很多的, 表妹(工厂)的责任太重, 包含(UML依赖关系)了所有的具体产品的实现. 另外, 如果需要修改或增加产品, 我们就得改变工厂类的实现. 这显然违反了开闭原则.

故而, 简单工厂, 一般来说, 适用于产品类别较少, 且固定的场景.