ANLY'S BLOG

是时候弄一套开分店的标准了---抽象工厂

前情提要

上集讲到, 小光(利用原型模式)Copy了光谷店的模式, 成功开张了创业街分店.

现在两家分店都运营得不错, 小光闲暇之时, 又陷入了思考(思考是个好习惯). 琢磨着, 这次开分店, 我是完全clone了光谷店的那一套, 然后修改了一些个属性(例如分店名字什么的). 但是, 以后小光热干面的发展, 还会开出很多分店呢, 而且没家分店可能不仅仅是名字不一样, 还有一些诸如饮料杯上的地址什么的也会不一样, clone起来是很方便, 但是万一哪次忘了改属性了, 就麻烦了.

想到着, 小光心想, 是时候制定一套开分店的标准流程/方式了.

所有示例源码已经上传到Github, 戳这里

分店都有什么

根据现有的经验, 小光很快总结出来了目前按照他的方式开一家热干面店基本包括的一系列东西:

  1. 店铺
  2. 收银台
  3. 杯具餐具
  4. 热干面流程
  5. 饮料机套路
  6. 活动策略

为便于区分讲解, 我们以前三个为例.

建立开分店的标准方式

制定每项东西标准

本着良好的面向接口编程思维, 小光照例给这些东西制定了一些标准(接口).

门面:

1
2
3
4
5
6
7
8
public interface Store {
// 地址
String getAddress();
// 店铺名
String getName();
}

收银台:

1
2
3
4
5
public interface Checkstand {
// 银行账户
String getAccount();
}
1
2
3
4
5
public interface Tableware {
// 标签
String getLabel();
}

统一配置生产

接下来, 小光要做的就是想着统一下每个分店开店的标准, 既要避免开分店时别遗漏了什么, 也能根据不同分店的配置开出不一样的分店. (开闭原则)

开分店的标准如下:

1
2
3
4
5
6
7
8
public interface CompanyFactory {
Store createStore();
Checkstand createCheckstand();
Tableware createTableware();
}

按照这个方式开分店

好的, 让我们来试下按照这个标准套路来开出SBI创业街分店.

先实现创业街分店的Store, Checkstand和Tableware:

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
public class SbiStore implements Store {
@Override
public String getAddress() {
return "关山创业街";
}
@Override
public String getName() {
return "SBI店";
}
}
public class SbiCheckStand implements Checkstand {
@Override
public String getAccount() {
return "招商银行:620123131231233";
}
}
public class SbiTableware implements Tableware {
@Override
public String getLabel() {
return "SBI";
}
}

然后, 实现一个SBI的专属工厂来生成这些东西:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class SbiCompanyFactory implements CompanyFactory {
@Override
public Store createStore() {
return new SbiStore();
}
@Override
public Checkstand createCheckstand() {
return new SbiCheckStand();
}
@Override
public Tableware createTableware() {
return new SbiTableware();
}
}

让我们利用SBI工厂生成的东西组建一个分店吧:

1
2
3
4
5
6
7
8
9
10
11
12
public class XiaoGuang {
public static void main(String[] args) {
CompanyFactory factory = new SbiCompanyFactory();
// 来根据factory生产出来的东西构建一个分店:
Company sbiCompany = new Company(factory.createStore(), factory.createCheckstand(), factory.createTableware());
System.out.println(sbiCompany);
}
}

结果:

1
分店{地址:关山创业街, 名字:SBI店, 收银账户:招商银行:620123131231233, 杯具餐具标签:SBI}

不负众望啊, 创建出来SBI店了.

以后我们就可以根据这种方式开分店了

例如, 如果想开一家花山软件新城店, 按照上面开创业街店的方式:

  1. 实现花山店的店铺 HuashanStore.
  2. 实现花山店的收银台 HuashanCheckStand.
  3. 实现花山店的餐具 HuashanTableware.
  4. 实现生产花山店的这些东西的工厂 HuashanCompanyFactory.

代码就不一一贴了, 完整代码戳这里.

来看下怎么开出花山分店:

1
2
3
4
5
6
7
// 创建Huashan工厂
factory = new HuashanCompanyFactory();
// 根据factory生产出来的东西构建花山店:
Company huashanCompany = new Company(factory.createStore(), factory.createCheckstand(), factory.createTableware());
System.out.println(huashanCompany);

结果:

1
分店{地址:花山软件新城, 名字:花山店, 收银账户:建设银行:62610000000000, 杯具餐具标签:HS}

好的, 花山店创建完毕.

故事之后

在这次建立创建分店的标准中, 小光一直想做就是: 确定分店的开设方式标准, 避免修改原有的分店产品, 方便扩展开出新的分店.

这个实际上就是我们屡次提到的开闭原则的思想, 即对修改关闭(不修改原有的), 对扩展开放(方便扩展出新的产品/实例).

惯例, 我们来梳理下类之间的关系:

没错, 这个就是我们今天的主角 — 抽象工厂模式.

抽象工厂
提供一个创建一系列相关或互相依赖的对象的接口, 而无需指定它们的具体实现.

在本例中这个接口就是CompanyFactory, 它创建了一系列相关的对象(Store, Checkstand, Tableware), 并且这些对象并非具体实现(也是接口). 具体的对象实例是由实现了CompanyFactory接口的HuashanCompanyFactory, SbiCompanyFactory来创建的.

扩展阅读一

到目前为止, 算上这个, 我们提到了三种工厂:

  1. 小光热干面提供饮料了 — 简单工厂
  2. 光氏饮品升级了 — 工厂方法
  3. 是时候弄一套开分店的标准了 — 抽象工厂

那么这三者有什么区别呢?

我们首先来对比下三者的类图:

简单工厂
实际上我们可以理解为是一种编程习惯, 将类似对象的初始化放下一个地方, 便于管理.
它提供了一个工厂(表妹), 来根据不同的指令(drinkType)来生产不同的饮料产品(橙汁, 可乐, 酸梅汤).
相对简单, 适用于要创建类似(实现同一接口的)的产品, 且产品种类不多, 扩展可能性不大的情况. 当需要增加一中饮料时, 我们需要修改工厂(表妹)的实现, 增加drinkType的对应实现.

工厂方法
顾名思义, 有一个工厂, 工厂(饮料机)里有那么一个方法(定义了一个创建对象的接口makeDrink), 可以生产产品(Drink). 由实现了这个工厂方法的类来决定具体生产出什么产品(可以是可乐, 橙汁, 奶茶等).

相比于简单工厂, 工厂方法有良好的扩展性, 当我们需要增加一种饮料时, 不需要去修改工厂, 只需扩展一个新的工厂, 实现其工厂方法, 提供新的饮料即可.

这实际上就是典型的, 通过继承/实现, 来达成了对修改关闭, 对扩展开放的效果.

另外, 从简单工厂到工厂方法, 我们也可以理解为是一次Switch Statements的重构.

对于”Switch Statements的重构”, 有兴趣的同学可以参看<<重构–改善既有代码的设计>>一书的3.10节. 那是一本好书, 2010年的时候华为的一位技术经理推荐给我的, 感谢他.

另外, 并不是我们以后遇到Switch就要想着改造, 遇到简单工厂就想着用工厂方法…还需根据实际情况取用合适的.

抽象工厂
同样, 从名字中, 我们大致能了解, 抽象工厂描述的一个抽象的工厂, 其可以生产一系列的相关的或是互相依赖的产品.

抽象工厂和工厂方法有很多类似之处, 都是创建产品, 都是通过继承/实现, 来达成了对修改关闭, 对扩展开放的效果.

然而, 抽象工厂相较于工厂方法, 它的重点, 是它解决的是一个产品族(相关的, 或是互相依赖的产品们)的创建问题, 而非仅仅是一类产品.

以本故事来说, 工厂方法是用来创建一类产品, 通过他创建出来的都是饮料. 而抽象工厂是用来创建一系列产品, 包括店铺, 收银台, 餐具等, 这些产品是相关的, 都是一个分店所需要的.

打个比方, 如果我有一个轮胎工厂, 我生产的东西都是轮胎, 只是规格不同, 我就可以使用工厂方法; 如果我是一个汽车工厂, 我生产汽车, 它需要轮胎, 车架, 发动机… 那么我就应用使用抽象工厂.

扩展阅读二

前文, 原型模式我们讲到, 原型工厂, 会经常跟工厂一起用. 那么它一般会与哪种工厂一起用呢, 没错, 就是我们今天说的抽象工厂.

为什么呢?
我们回过头来看下, 我们抽象工厂创建的过程, 是不是创建了很多对象啊. 是的, 抽象工厂如果生产的产品系列很多的话, 我们会发现我们会创建很多很多的对象(产品), 而这些对象有的时候是可能一样的, 有可能是很类似的.

例如, 我们的分店工厂创建了收银台(Checkstand), 这个不同的分店的收银台可能都是一样的, 稍微有点不同可能是仅仅是账户不一样, 我们没有必要去重新创建一个.

回想下, 原型模式的使用 — 使用clone的方式来快速创建一个新的(与原型对象实例一致的)对象实例. 这种方法可以避免我们屡次复杂的创建.

我们当前的Checkstand创建可能相对简单, 想象下, 如果Checkstand足够复杂的时候, clone将会是很有用的.

具体的实现, 大家可以私下去改造下本例代码, 融合下原型模式, 体验下.


OK, 创建出开分店的标准了, 小光想着, 以后开分店都不用我亲力亲为的…哈哈.