开发日常
产品:我只要一台宝马车?!
研发:… … 然后呢
产品:你先开发出来看看,就某某App那种… ...
研发:华晨宝马?
产品:对,就照到它做
研发:… 一脸无奈,需求能具体点不?麻烦细化需求文档发邮件出来吧,记得抄送老板!
以上内容,纯属虚构,如有雷同,请留言告诉我 😁。
下面进入正题,本文依次介绍 简单工厂、工厂方法 和 抽象工厂,使用需求+示例程序方式,帮助你理解模式的实现、适用场景以及它们之间的差异。
简单工厂
现在假设有这样一个需求:根据用户订单,生产并交付不同车型的宝马汽车,比如x1、x3、x5等。
识别变化的方面
不考虑任何设计,我们的代码可能会这样写:
不久后,市场反馈消费者对高端SUV需求很大,x5还满足不了,所以新的车型x7就来了。同时,市场也反馈x3卖的不好,所以考虑将其停产。
不难看出,此代码''没有''对修改关闭,不符合开闭原则。问题就在实例化''某些''具体类(这里是指生产具体的宝马汽车)。这时,你可能会想到将易变的部分逻辑封装起来不就好了,好主意,马上试试。
封装创建对象的代码
这里我们将易变的逻辑(生产汽车)代码,放到了一个名叫SimpleCarFactory的新类中,由它来负责生产汽车。
这样orderCar方法就不用管汽车生产了,只需拿到工厂生产的汽车进行后续流程即可。
现在我们的代码设计,就是民间所说的 "简单工厂"。简单工厂其实并不是一种设计模式,它更像是一种编程习惯。
关于''简单工厂'',我们可能会有疑问?
-
Q: 这样做有什么好处?似乎只是将问题搬到另一个类罢了,问题依旧存在。
A:1. SimpleCarFactory作为工厂类,可能会有多个客户,如果实现要改变,只需修改这个类就可。2. 封装,提高代码复用。3. 也不建议将具体实例化过程放到客户程序代码中。
假设需求升级了,我们不仅要生产SUV,现在还要生产轿车了,以后可能还会生产跑车、新能源车等等。随着需求变化,之前的简单工厂模式已不太适合,所以本文的主角 工厂方法 模式登场了。
工厂方法(Factory Method)模式
定义:定义一个用于创建对象的接口,让子类决定实例化哪个类。Factory Method使一个类的实例化延迟到其子类。
在Factory Method模式中,父类决定实例的生成方式,但并不决定所要生成的具体类,具体的处理全部交给子类负责。这样就将生成实例的框架和实际负责生成实例的类解耦。
示例程序
下面我们就用工厂方法模式,实现生产宝马汽车的需求。先来看看示例程序的类图。
framework(框架)包: Car 接口和Factory抽象类封装了生成实例的框架。Factory类依赖了接口Car。
suv(具体实现)包: BmX1xDrive20i和BmX1XDrive28I为具体产品类,实现至Car接口。X1Factory类为具体工厂类,继承至Factory抽象类,负责具体产品的创建。
抽象产品接口:Car
Car 属于framework(框架)包,它定义了接口getName来确定汽车名称。
public interface Car {
@NonNull
String getName();
}
抽象工厂类:Factory
Factory 类属于framework(框架)包,它声明了用于生产汽车的createCar抽象方法和orderCar模板方法。
public abstract class Factory {
/**
* 生产汽车
* @param type 车型。比如320、328、525等。
*/
public abstract Car createCar(String type);
public final void orderCar(String type) {
Car car = createCar(type);
System.out.println("生产汽车完成 " + car.getName());
test(car.getName());
shangpai(car.getName());
baoxian(car.getName());
gogo();
}
public void test(String name) {
System.out.println(name + " : 通过测试 ");
}
public void shangpai(String name) {
System.out.println(name + " : 上牌完成 ");
}
public void baoxian(String name) {
System.out.println(name + " : 已购买保险 ");
}
public void gogo() {
System.out.println(" 一切都 OK, 出去浪 ");
}
}
具体产品类:BmX1xDrive20i 和 BmX1xDrive28i
为了能够明显地体现出与框架的分离,这里将具体产品类放到suv包下。
public class BmX1xDrive20i extends Car {
@NonNull
@Override
public String getName() {
return "x1: xDrive20i";
}
}
public class BmX1xDrive28i extends Car {
@NonNull
@Override
public String getName() {
return "x1: xDrive28i";
}
}
具体工厂类:X1Factory
public class X1Factory extends Factory {
@Override
public Car createCar(String type) {
Car car = null;
switch (type) {
case "20i":
car = new BmX1xDrive20i();
break;
case "28i":
car = new BmX1xDrive28i();
break;
default:
throw new RuntimeException("type parameter is invalid : "+type);
}
return car;
}
}
客户代码调用类:Main
public class Main {
public static void main(String[] args) {
Factory factory = new BmX1Factory();
factory.orderCar("20i");
System.out.println("-------------------");
factory.orderCar("28i");
System.out.println("-------------------");
}
}
// 运行结果
生产汽车完成 x1: xDrive20i
x1: xDrive20i : 通过测试
x1: xDrive20i : 上牌完成
x1: xDrive20i : 已购买保险
一切都 OK, 出去浪
-------------------
生产汽车完成 x1: xDrive28i
x1: xDrive28i : 通过测试
x1: xDrive28i : 上牌完成
x1: xDrive28i : 已购买保险
一切都 OK, 出去浪
-------------------
模式的参与者(角色)
看了上面的示例代码,是不是觉得工厂方法模式很简单?是的,而且它应用也很广泛。示例程序只是展示了它的用法和使用场景,为加深理解,我们一起来看看该模式有哪些参与者,它们在模式中都担任什么角色?
- Product :产品角色,接口或抽象类。它定义生成实例所需的接口(API),但具体处理由子类ConcreteProduct角色决定。对应示例程序中的Car类。
- Creator:创建者(抽象工厂)角色,它是负责生成Product角色的抽象类。但具体处理由子类ConcreteCreator角色决定。对应示例程序中的Factory类。
- ConcreteProduct:具体的产品角色,它决定了具体的产品。对应示例程序中的BmxDrive20i类。
- ConcreteCreator:具体的创建者(工厂)角色,它负责生成具体的产品。对应实例程序中的X1Factory类。
- client:客户代码,可忽略。
拓展思路
在上面的示例程序中,抽象产品Car类和抽象工厂Factory类是放在framework(框架)包下的,具体工厂X1Factory类和具体产品类是放在suv(具体实现)包下的。
假设现在我们要新增一个产品,比如要生产宝马5系轿车了。这时,我们只需创建具体产品类比如:Bm525li、Bm540li,外加一个具体工厂类Bm5SeriesFactory就能完成代码扩展。
请注意,这个过程我们没有修改framework包下任何内容,就创建出了新的 "产品" 。这就说明该模式可以将封装生成实例的框架和实际负责生成实例的类解耦, 符合开闭原则设计。
小结
工厂方法(Factory Method) 模式要点:将实例的生成交给子类。
使用场景
- 产品种类多或可能新增产品时。
- 当一个类希望其子类来指定它所创建的对象时。
问答/交流
-
Q: 当只有一个ConcreteCreator的时候,工厂方法的模式有什么优点?
A:尽管只有一个具体创建者,工厂方法模式依然很有用,因为它帮助我们将产品的 "实现" 从 "使用" 中解耦。如果增加产品或改变产品的实现,Creator并不会受到影响。
-
Q:工厂方法和创建者是否总是抽象的?
A:不是。可以定义一个默认的工厂方法来生产某些具体的产品,这样一来,即使创建者没有任何子类,依然可以创建产品。
-
Q:工厂方法与简单工厂的关系?
A:工厂方法是简单工厂的进一步抽象,它克服了简单工厂违背开发-封闭原则的缺点,又保持了封装对象创建过程的优点。
-
Q:工厂方法与简单工厂都是对创建过程的封装,且客户程序用法都差不多,那它们之间实质的区别是啥呢?
A:实现方式不同:工厂方法使用了继承,而简单工厂只有封装。 这也导致它们存在以下差异:
- 简单工厂不能通过继承来改变创建方法的行为,而工厂方法可以。
- 增删具体 "产品" 时,简单工厂通过修改if else等判断语句实现,工厂方法通过继承、多态属性实现。
-
Q:工厂方法与简单工厂适用场景?该用哪个,怎么选?
A:1. 代码设计要满足 开闭原则 选工厂方法。2. 看具体业务场景、现有项目代码结构、个人偏好。
抽象工厂(Abstract Factory)模式
定义:为创建一组相关或者是相互依赖的对象提供一个接口,而不需要指定它们的具体类。
适用场景:
- 一个系统要由多个产品系列中的一个来配置时。
- 当你要强调一系列相关的产品对象的设计以便进行联合使用时。
- 当你提供一个产品类库,而只想显示它们的接口而不是实现时。
以上对 抽象工厂(Abstract Factory) 模式的定义和适用场景内容,摘抄至经典书籍 “GOF-设计模式" 。如果觉得难理解,没关系,我们先来看看下面的示例程序。
示例程序
延用工厂方法模式那个示例程序,需求调整为,不同的车型需要配置不同的发动机、轮胎、刹车等零件。比如:7系车型的零件需要全进口,3系车型的零件则尽量使用国产的,还有5系车型等零件要求都不一样。
从示例程序类图中可以看出,一共有三个程序包:factory、concrete、x1factory。
factory(框架)包:接口类IBrake、ITire、IEngine为抽象零件接口,属于抽象产品类Car的一部分(组合关系);抽象的零件工厂类ComponentFactory依赖了定义的零件接口,抽象的产品类CarFactory同时依赖了Car类和ComponentFactory类。其中抽象产品类CarFactory,使用这些接口(API)封装了创建产品的逻辑。
concrete(具体零件)包: NormalBrake、NormalTire、DomesticEngine三个类为具体零件类;NormalComponentFactory为具体的零件工厂类,负责生产具体的零件。
x1factory(具体产品)包: X1表示具体的产品类继承至抽象类Car;X1Factory为具体的产品工厂继承至抽象工厂CarFactory,它负责生产具体的产品(X1)。
在Abstract Factory模式中,不仅有 "抽象工厂", 还有 "抽象零件" 和 "抽象产品"。 抽象工厂的作用是将 "抽象零件" 组装为 "抽象产品" 。好像是有点抽象,那就先看看示例程序代码实现吧。
抽象的零件接口:IBrake、ITire、IEngine
public interface IBrake {
void brake();
}
public interface ITire {
void tire();
}
public interface IEngine {
void engine();
}
抽象的零件工厂类:ComponentFactory
public abstract class ComponentFactory {
public abstract IBrake createBrake();
public abstract ITire createTire();
public abstract IEngine createEngine();
}
抽象的商品类:Car
public abstract class Car {
protected IBrake mBrake;
protected IEngine mEngine;
protected ITire mTire;
public void setBrake(IBrake brake) {
this.mBrake = brake;
}
public void setEngine(IEngine engine) {
this.mEngine = engine;
}
public void setTire(ITire tire) {
this.mTire = tire;
}
// 车型名称
public abstract String getName();
// 价格
public abstract String getPrice();
@Override
public String toString() {
StringBuilder stringBuilder = new StringBuilder();
stringBuilder
.append("使用的发动机:").append(mEngine).append('\n')
.append("使用的轮胎:").append(mTire).append('\n')
.append("使用的刹车:").append(mBrake);
return stringBuilder.toString();
}
}
抽象的产品工厂类:CarFactory
public abstract class CarFactory {
//准备生产该车型的零件
protected final void prepare(Car car) {
ComponentFactory factory = componentFactory();
car.setmBrake(factory.createBrake());
car.setmTire(factory.createTire());
car.setmEngine(factory.createEngine());
}
//组装汽车
public final void assembleCar() {
Car car = createCar();
System.out.println("开始生产 :" + car.getName());
prepare(car);
System.out.println(car);
System.out.println("生产完成 :" + car.getName() + "; 售价:" + car.getPrice());
test(car.getName());
shangpai(car.getName());
baoxian(car.getName());
gogo();
}
//子类提供具体的零件工厂
protected abstract ComponentFactory componentFactory();
// 子类提供需要生产的具体车型
public abstract Car createCar();
...
}
具体的零件类:NormalBrake、NormalTire、DomesticEngine
public class NormalBrake implements IBrake {
String name = "普通刹车系统";
@Override
public void brake() {
System.out.println("name = " + name);
}
@Override
public String toString() {
return name;
}
}
public class NormalTire implements ITire {
String name = "普通轮胎";
@Override
public void tire() {
System.out.println("name = " + name);
}
@Override
public String toString() {
return name;
}
}
public class DomesticEngine implements IEngine {
String name = "国产发动机";
@Override
public void engine() {
System.out.println("name = " + name);
}
@Override
public String toString() {
return name;
}
}
具体的零件工厂类:NormalComponentFactory
public class NormalComponentFactory extends ComponentFactory {
@Override
public IBrake createBrake() {
return new NormalBrake();
}
@Override
public ITire createTire() {
return new NormalTire();
}
@Override
public IEngine createEngine() {
return new DomesticEngine();
}
}
具体的产品类:X1
public class X1 extends Car {
@Override
public String getName() {
return "BM-X1-xDrive-28i";
}
@Override
public String getPrice() {
return "40W";
}
}
具体的产品工厂类:X1Factory
public class X1Factory extends CarFactory {
@Override
protected ComponentFactory componentFactory() {
return new NormalComponentFactory();
}
@Override
public Car createCar() {
return new X1();
}
}
客户代码调用类:Main
public class Main {
public static void main(String[] args) {
System.out.println("-------------------");
CarFactory x1Factory = new X1Factory();
x1Factory.assembleCar();
System.out.println("-------------------");
}
}
//运行结果
-------------------
开始生产 :BM-X1-xDrive-28i
使用的发动机:国产发动机
使用的轮胎:普通轮胎
使用的刹车:普通刹车系统
生产完成 :BM-X1-xDrive-28i; 售价:40W
CarFactory : 通过测试
CarFactory : 上牌完成
CarFactory : 已购买保险
CarFactory : 出去浪
-------------------
到这里我们就用 抽象工厂(Abstract Factory) 模式,生产出了一个具体产品(BM-X1汽车)。现在给我的感觉就是抽象工厂模式阵势好大,生产一个产品,创建这么多类。当然如果只有一个具体产品工厂时,是没必要划分 "抽象类" 和 "具体类" 的,也就是说没必要使用抽象工厂模式(后面会提到该模式的使用场景)。接下来,我们就在示例程序中增加其它的具体工厂(生成BM-X7汽车)。
添加其它具体工厂(产品)
新增具体的零件类:AdvancedBrake、AdvancedSuvTire、ImportEngine
public class AdvancedBrake implements IBrake {
String name = "高性能刹车系统。";
@Override
public void brake() {
System.out.println("高性能刹车系统。");
}
@Override
public String toString() {
return name;
}
}
public class AdvancedSuvTire implements ITire {
String name = "高性能SUV专用轮胎。";
@Override
public void tire() {
System.out.println(name);
}
@Override
public String toString() {
return name;
}
}
public class ImportEngine implements IEngine {
String name = "进口发动机。";
@Override
public void engine() {
System.out.println(name);
}
@Override
public String toString() {
return name;
}
}
新增具体的零件工厂类:AdvancedComponentFactory
public class AdvancedComponentFactory extends ComponentFactory {
@Override
public IBrake createBrake() {
return new AdvancedBrake();
}
@Override
public ITire createTire() {
return new AdvancedSuvTire();
}
@Override
public IEngine createEngine() {
return new ImportEngine();
}
}
新增具体的产品类:X7
public class X7 extends Car {
@Override
public String getName() {
return "BM - X7";
}
@Override
public String getPrice() {
return "200W";
}
}
新增具体的工厂类:X7Factory
public class X7Factory extends CarFactory {
@Override
protected ComponentFactory componentFactory() {
return new AdvancedComponentFactory();
}
@Override
public Car createCar() {
return new X7();
}
}
修改客户代码调用类:Main
public class Main {
public static void main(String[] args) {
System.out.println("-------------------");
CarFactory x1Factory = new X1Factory();
x1Factory.assembleCar();
System.out.println("-------------------");
CarFactory x7Factory = new X7Factory();
x7Factory.assembleCar();
System.out.println("-------------------");
}
}
//运行结果
-------------------
开始生产 :BM-X1-xDrive-28i
使用的发动机:国产发动机
使用的轮胎:普通轮胎
使用的刹车:普通刹车系统
生产完成 :BM-X1-xDrive-28i; 售价:40W
CarFactory : 通过测试
CarFactory : 上牌完成
CarFactory : 已购买保险
CarFactory : 出去浪
-------------------
开始生产 :BM - X7
使用的发动机:进口发动机。
使用的轮胎:高性能SUV专用轮胎。
使用的刹车:高性能刹车系统。
生产完成 :BM - X7; 售价:200W
CarFactory : 通过测试
CarFactory : 上牌完成
CarFactory : 已购买保险
CarFactory : 出去浪
-------------------
很容易就完成了对高级豪华车BM-X7生产,整个过程中factory包和其它已存在的类都没被修改(Main类除外),只是新增几个类就轻松完成扩展。这里可以看出抽象工厂模式的一大优点:满足开闭原则。
模式参与者(角色)
下面我们从 抽象工厂(Abstract Factory) 模式类图来认识模式中的参与者(角色),认清它们的职责,并与上面的示例程序关联起来。
AbstractProduct 角色负责定义Abstract Factory 角色所生成的抽象零件和产品的接口(API)。对应示例程序中的IBrake、ITire、IEngine、Car类。
AbstractFactory(抽象工厂)
AbstractFactory 角色负责定义用于生产抽象产品的接口(API)。对应实例程序中的ComponentFactory(抽象零件工厂)、CarFactory(抽象产品工厂)类。
可能你会问,为什么示例程序要定义两个工厂呢?我的回答是:从一系列 "产品" 中区分 "零件" 和 "产品" ,有助于理解它们之间的关系; 另外,将它们独立开来,可减少编写模板代码,提高代码复用。
ConcreteProduct(具体产品)
ConcreteProduct 角色负责实现AbstractProduct角色的接口(API)。对应示例程序中的NormalTire、NormalBrake、ImportEngine、X1、X7等类。
ConcreteFactory(具体工厂)
ConcreteFactory 角色负责实现AbstractFactory角色的接口(API)。对应示例程序中的NormalComponentFactory、X1Factory等类。
拓展思路
易于添加具体的工厂
在Abstract Factory模式中添加具体工厂是很容易的。思路清楚,按接口实现对应方法即可。
假设现在我们要为示例程序添加具体的工厂和产品(X5),那么需要做的就是编写Car、CarFactory这两个类的子类,并实现它们定义的抽象方法即可。这里没有创建具体的零件及零件工厂,有个前提条件是新增的具体产品(X5)可以复用已存在的零件工厂(比如:AdvancedComponentFactory)。即使该条件不成立,也只需多创建一个抽象零件工厂的子类即可完成扩展。
难以添加新的零件
试想一下要在Abstract Factory模式中添加新的零件时应当怎么做。假设,我们要在示例程序的factory包中增加一个表示变速箱的gearbox零件。这时我们要修改抽象工厂和已存在的所有具体工厂,改的多,工作量大,出错风险也高。
小结
抽象工厂(Abstract Factory) 模式允许客户使用抽象的接口来创建一组相关的产品,而不需要知道实际产品的具体产品是什么,这样客户和具体的产品实现解耦。
抽象工厂(Abstract Factory) 模式:提供一个接口,用于创建相关或相互依赖对象的家族,而不需要明确指定具体类。 — 摘抄至 《Head First 设计模式》
问答/交流
-
Q: 抽象工厂(Abstract Factory) 模式类图中只有一个工厂类,为什么示例程序有两个?
A:由于示例程序将 "产品" 细分为 "零件" 和 "产品" ,所以较模式类图多了一个 "抽象零件工厂''。这样设计是为了帮助我们理解各个 ''产品'' 之间的关系;另外,将它们分开处理,可减少编写模板代码,提高代码复用。当然,你完全可以将 ''抽象零件工厂'' 的工作放到 ''抽象产品工厂'' 中一并处理,这里没有标准,具体可视业务情况而定。
-
Q:抽象工厂(Abstract Factory) 模式的适用场景?
A:场景1. 存在多个 "产品" 且 它们之间有关系,这种关系最好是 ''零件'' 与 ''整体'' 的从属关系。
场景2. 一个对象族有相同的约束时就可以使用抽象工厂模式。(比如,需要实现不同操作系统下的文本和 图片软件,它们共同的约束为:操作系统。— 内容来至: 设计模式之禅。)
一言以蔽之:当你需要创建产品家族 或者 想让创建的相关产品组装(集合)起来时。
-
Q:工厂方法是不是潜伏在抽象工厂里面?
A:是的。由于抽象工作定义了创建一组产品的接口(API),这里的每一个方法都负责创建一个具体的产品,这些产品的具体创建过程也是在子类完成的,这里也就用到了工厂方法模式。
总结
- 所有的工厂都是将对象创建的过程封装起来。以便将客户代码与具体类解耦。
- 工厂方法使用继承,把对象的创建委托给子类,子类实现工厂方法来创建对象。
- 抽象工厂创建相关的对象家族,而不需要依赖它们的具体类。
- 工厂方法和抽象工厂都帮助我们针对抽象编程,引导我们设计出符合 依赖倒置原则 的代码。
相关模式
-
Template Method 模式
Factory Method 模式是 Template Method 模式的典型应用。示例程序的抽象工厂类中,orderCar方法就是模板方法。
-
Singleton 模式
工厂类实例可使用单例模式。因为大部分情况下程序中没必要存在多个具体工厂角色的实例。
编码建议
- 多用组合,少用继承。
- 依赖抽象,不要依赖具体类。
- 类应该对扩展开发,对修改关闭。
- 为交互对象之间的松耦合设计而努力。
参考与推荐
- 《图解设计模式》
- 《Head First 设计模式》
- 《大话设计模式》
- 《设计模式之禅》
- 《设计模式-可复用面向对象软件基础》
- Runoob.com 设计模式
写在最后,这篇文章是去年团队分享时写的,文中观点和结论是团队讨论的成果,这里分享出来希望对看到这里的你有帮助,如果你有不同的想法或建议,请留言,一起交流。