设计模式(四)工厂模式
我们在第一次学习对象这个概念的时候,新建对象用的是这样的代码:
Product product = new Product();
但是真正到了工程上这样的代码是“生硬”的,当遇到 Product 改变,或者 Product 初始化流程更改这样的新需求时,要改变的代码量相当的大。我们需要在找出所有调用 Product 构造器的地方,并为之添加上新的代码,这样的代码设计显然是不符合设计原则的。
为了改善上面的情况,工厂模式应运而生。
工厂模式是一种创建模式。创建模式对类的实例化过程进行了抽象,能将类的创建和使用分离。工厂类将产品类的初始化封装在方法之中,对于外界而言,只要知道产品类的公共接口,就可以正常实现功能,这使得各个模块开发时只专注于自己的业务逻辑,忽略对象的创建细节。
由于场景不同,我们可以看到不同形态的工厂模式,一般可以分为以下几种:
- 简单工厂模式
- 工厂方法模式
- 抽象工厂模式
三种模式间并没有绝对的优劣,因为各个模式各有优缺,需要根据特定的场景来选择。但是大多数开发场景中,我们并没有这样庞大的系统,因此,通常我们使用的还是前两种方法。
一、简单工厂模式
简单工厂模式是一种类创建模式,通常情况下,简单工厂的对象会有一个静态方法用于创建产品,并且这个方法通常根据传入的参数来生产指定的产品。这种工厂模式即使你不知道有设计模式这一说法,也会自行使用,因为它构造简单,实现方便,也确实能在项目中实现立竿见影的效果。
1)简单工厂模型
SimpleFactory.png先来看看最简单的实现代码:
public class SimpleFactory {
public static Product createProduct(String arg){
Product product = null;
switch (arg) {
case "A":
product = new ProductA();
// init something
break;
case "B":
product = new ProductB();
// init something
break;
default:
throw new RuntimeException("product not exist for arg: "+arg);
}
return product;
}
}
2)使用场景
对于产品种类少,且产品相对固定,出现新种类产品的可能性小的时候,可以使用简单工厂模式
我们假定一个简单的场景,依旧用上一篇建造者模式中提到的电脑。
现在电脑都可以办公和学习,因此,我们定义一个公共接口:
public interface IComputer {
void work();
void study();
}
然后,现在一个人有一台电脑用来工作或者学习,但是这台电脑是有配置高低分别的,这时候我们需要两个类实现上面的接口,并且在构造器里,分别配置它的硬件信息:
public class ComputerA implements IComputer {
private static final String TAG = "ComputerA";
public ComputerA(){
//初始化高端硬件配置
Log.i(TAG, "ComputerA: 初始化高端硬件配置");
}
@Override
public void work() {
//高端机工作
Log.i(TAG, "ComputerA: 高端机工作");
}
@Override
public void study() {
//高端机玩学习
Log.i(TAG, "ComputerA: 高端机玩学习");
}
}
public class ComputerB implements IComputer {
private static final String TAG = "ComputerB";
public ComputerB(){
//初始化普通硬件配置
Log.i(TAG, "ComputerA: 初始化普通硬件配置");
}
@Override
public void work() {
//普通机工作
Log.i(TAG, "ComputerA: 普通机工作");
}
@Override
public void study() {
//普通机玩学习
Log.i(TAG, "ComputerA: 普通机玩学习");
}
}
通过构造器,我们已经可以获取到不同硬件配置的电脑,然后我们需要对不同电脑安装适合的工作学习软件:
public class SimpleFactory {
public static IComputer createProduct(String arg){
IComputer product = null;
switch (arg) {
case "A":
product = new ComputerA();
// 下载学习软件 M
// 下载工作软件 N
// init something
break;
case "B":
product = new ComputerB();
// 下载学习软件 X
// 下载工作软件 Y
// init something
break;
default:
throw new RuntimeException("product not exist for arg: "+arg);
}
return product;
}
}
下面在想要工作学习的地方,我们只需要传入所需电脑对应的名称,就可以获取我们需要的电脑并且实现我们工作学习的目的:
IComputer iComputerA = SimpleFactory.createProduct("A");
iComputerA.study();
iComputerA.work();
IComputer iComputerB = SimpleFactory.createProduct("B");
iComputerB.study();
iComputerB.work();
下面是这个过程的日志图:
效果图-g.png
对比代码和日志,我们可以看到,由于简单工厂的封装,在需要使用电脑工作学习的时候,我们只要知道当前需要的电脑名称:“A” 或 “B”,就可以获取我们的电脑,并且通过统一的接口方法实现工作学习的目的。
这样做,显然使得代码变得更加简洁,对于业务而言逻辑也变得清楚,因为无关的东西都被隐藏了起来。
3)优缺点
**优点:
构造容易、逻辑简单。只要通过一个工厂方法就可以得到需要的对象,不用关注这个对象是如何构造出来。工厂模式能略过了产品的初始化细节,并且规范统一了所有产品功能的调用方法。
**缺点:
由于简单工厂所有产品的初始化方法都在一个方法中,当产品数量过多且初始化过程复杂的时候,代码会显得过长且难以理解。同时,通过标志位分辨生产产品的类型,使得代码内部耦合严重,如果需要新添加一个产品 C ,就必须要修改这个工厂类对象,这显然不符合前面提到的 开放封闭原则(ASD) ,即对拓展开放,对修改封闭。优秀的设计应该能够在有新产品的时候,尽量保持原代码不变,只添加新代码就能实现功能。这一点,简单工厂模式无法达到。
那么这一点,我们需要怎样达到呢?于是,工厂方法模式出现了。
二、工厂方法模式
工厂方法模式也是一种类创建模式,与简单工厂不同的是,它不再通过参数来区分当前生产的产品对象。工厂方法模式将工厂抽象化,定义了一个公共父类用于规定创建产品对象的公共接口,并把所有产品实力化的时间延迟到工厂的子类。文字说明有些不好理解,先来看一下图解。
MethodFactory.png由图可以看到,对应产品 A 有指定的 FactoryA 用于生产,对应产品 B 有指定的 FactoryB 来生产。FactoryA 和 FactoryB 都是 IFactory 的子类,如果想要生产新的产品 C ,只需要多加一个 IFactory 子类用于生产 C ,而不需要改动原有代码。因此这种模式,也叫做多态工厂模式。
还是用上面的电脑例子,现在我们要多加一个生产工厂,生产低端电脑:
MethodFactory2.png工厂方法模式遵循[开放封闭原则(ASD)],但是它也有缺点,即完成相同的功能,对于工厂方法而言要新增一个工厂类一个产品类,文件数量多,使得系统变得复杂。
而现在问题又来了,高端、中端、低端电脑都有了,但是实际上,对于电脑我们不只有配置高低,还有品牌。现在高端、中端、低端电脑提供商有两家,一家是联想,一家是宏基。在这样的情况下, 工厂方法模式的设计并没有办法让我们获取我们需要的品牌的电脑。于是,我们用到了抽象工厂模式。
三、抽象工厂模式
抽象工厂模式是对工厂模式的抽象。事实上,抽象工厂模式是最具有一般性质的工厂模式。为了了解这个最为抽象的模式,首先,我们需要了解几个概念,我们还是会以熟悉的电脑为例子进行说明:
- **产品继承结构:例如高端电脑是一个类别,而联想高端电脑和宏基高端电脑,都是这个类别的子类,实现了它的功能接口。
- **产品族:产品族是指由同一个工厂生产的,位于不同产品等级结构中的一组产品。这里是指同个品牌下的一系列产品,例如联想有高端、中端、低端的电脑产生流水线,同样的宏基也有,而这高端、中端、低端的三种电脑,就是他们的产品族
在这里,例子中的高端、中端和低端更换为商务本、游戏本和二合一的平板笔记本可能更合适些。不过,管他呢。
一个抽象工厂模式一般包含如下角色:
AbstractFactory:抽象工厂
ConcreteFactory:具体工厂
AbstractProduct:抽象产品
Product:具体产品
先看下图解:
AbstractFactory.png
可以看到,不同品牌的生产工厂分别实现了抽象工厂的接口,将自己的生产过程封装起来,在我们需要电脑工作学习的时候,只需要根据需要的品牌选取合适的工厂类,然后利用工厂类选择合适的机型,就可以获取我们需要的电脑对象:
public interface IComputerFactory {
IComputer createComputerA();
IComputer createComputerB();
}
public class LenovoComputerFactory implements IComputerFactory {
private static final String TAG = "LenovoComputerFactory";
public LenovoComputerFactory(){
Log.i(TAG, "LenovoComputerFactory: 联想电脑工厂初始化");
}
@Override
public IComputer createComputerA() {
Log.i(TAG, "createComputerA: 联想电脑工厂制造高级配置电脑");
return new ComputerA();
}
@Override
public IComputer createComputerB() {
Log.i(TAG, "createComputerB: 联想电脑工厂制造普通配置电脑");
return new ComputerB();
}
}
调用日志打印:
效果图-g.png
抽象工厂模式的优点在于,新添加产品族是符合[开放封闭原则(ASD)],但是新增产品的继承结构却是不符合的,在具体项目中需要权衡倾向,再作应用。
感谢:
以上。