1 工厂模式的作用,为什么要用工厂模式?
工厂模式是23种设计模式之一,类属于创建型的模式,也就是主要负责创建对象的模式。
工厂模式可以一定程度上解耦:把对象的创建和使用的过程分开。就是Class A 想调用Class B,那么只是调用B的方法,而至于B的实例化,就交给工厂类。这样做还有一个好处就是当业务变动时,需要将Class B 替换成Class C时,只需要修改工厂类里面的相应处理即可,不需要去动业务里面的代码(当然,B跟C要实现同一个实现定义好的接口);
工厂模式可以降低代码重复。如果创建B过程都很复杂,需要一定的代码量,而且很多地方都要用到,那么就会有很多的重复代码。可以把这些创建对象B的代码放到工厂里统一管理。既减少了重复代码,也方便以后对B的维护;
工厂模式可以减少错误,因为工厂管理了对象的创建逻辑,使用者不需要知道具体的创建过程,只管使用即可,减少了使用者因为创建逻辑导致的错误。
2 工厂模式的一些适用场景(不仅限于以下场景)
对象的创建过程/实例化准备工作很复杂,需要很多初始化参数,查询数据库等;
类本身有好多子类,这些类的创建过程在业务中容易发生改变,或者对类的调用容易发生改变。
3 工厂模式的分类
- 简单工厂
- 工厂方法
- 抽象工厂
下面用生产电脑来举例来分析。
3.1 简单工厂
简单工厂模式(Simple Factory Pattern)是指由一个工厂对象决定创建出哪一种产品类的实例。简单工厂适用于工厂类负责创建的对象较少的场景,且客户端只需要传入工厂类的参数,对于如何创建对象的逻辑不需要关心。
创建电脑实体(为了方便演示,这里用了lombok的注解)
@Setter
@ToString
public class ComputerModel {
/** 型号 */
private String type;
/** 主机 */
private String mainEngine;
/** 显示器 */
private String monitor;
}
创建电脑抽象
public interface Computer {
/** 品牌 */
String getBrand();
/** 获取电脑实体信息 */
ComputerModel getComputerModel();
}
创建香蕉牌电脑类
public class BananaComputer implements Computer {
private ComputerModel computerModel;
public BananaComputer(ComputerModel computerModel) {
this.computerModel = computerModel;
}
@Override
public String getBrand() {
return "香蕉";
}
@Override
public ComputerModel getComputerModel() {
return computerModel;
}
}
创建戴尔牌电脑类
public class DellComputer implements Computer {
private ComputerModel computerModel;
public DellComputer(ComputerModel computerModel) {
this.computerModel = computerModel;
}
@Override
public String getBrand() {
return "戴尔";
}
@Override
public ComputerModel getComputerModel() {
return computerModel;
}
}
创建简单电脑工厂
public class ComputerFactory {
public static Computer create(String name) {
if ("banana-01".equals(name)) {
ComputerModel computerModel = new ComputerModel();
computerModel.setType("香蕉一代");
computerModel.setMainEngine("联想的主机");
computerModel.setMonitor("三星的显示器");
return new BananaComputer(computerModel);
} else if ("dell-11".equals(name)) {
ComputerModel computerModel = new ComputerModel();
computerModel.setType("戴尔一代");
computerModel.setMainEngine("dell的主机");
computerModel.setMonitor("dell的显示器");
return new DellComputer(computerModel);
} else {
return null;
}
}
}
测试
public class SimpleFactoryTest {
public static void main(String[] args) {
Computer banana = ComputerFactory.create("banana-01");
System.out.println(banana.getBrand());
System.out.println(banana.getComputerModel().toString());
System.out.println("------------------------------------");
Computer dell = ComputerFactory.create("dell-11");
System.out.println(dell.getBrand());
System.out.println(dell.getComputerModel().toString());
}
}
输出
香蕉
ComputerModel(type=香蕉一代, mainEngine=联想的主机, monitor=三星的显示器)
------------------------------------
戴尔
ComputerModel(type=戴尔一代, mainEngine=dell的主机, monitor=dell的显示器)
我们再来看一下UML类图
通过上面的例子可以看到我们的工厂是通过传入的不同参数来生成相应的对象,并且该对象内部的配件也是由工厂来决定的,假设有个客户过来说“我要戴尔一代电脑”,只需要传个“dell-11”参数给工厂的create方法就行了,不需要关注这个电脑是怎么造的,用的什么配件。
如果业务需求变化,我们的要支持生产更多的电脑,则要创建相应的电脑类,并在工厂类的逻辑里面加上相应的参数处理逻辑,返回相应的电脑对象。
3.2 工厂方法
工厂方法模式(Fatory Method Pattern)是指定义一个创建对象的接口,但让实现这个接口的类来决定实例化哪个类,工厂方法让类的实例化推迟到子类中进行。在工厂方法模式中用户只需要关心所需产品对应的工厂,无须关心创建细节,而且加入新的产品符合开闭原则。
工厂方法模式主要解决产品扩展的问题,在简单工厂中,随着产品链的丰富,如果每个课程的创建逻辑有区别的话,工厂的职责会变得越来越多,有点像万能工厂,并不便于维护。根据单一职责原则我们将职能继续拆分,专人干专事。
创建电脑工厂接口
public interface ComputerFactory {
/** 生产电脑 */
Computer create();
}
再分别创建子工厂
public class BananaComputer01Factory implements ComputerFactory {
@Override
public Computer create() {
ComputerModel computerModel = new ComputerModel();
computerModel.setType("香蕉一代");
computerModel.setMainEngine("联想的主机");
computerModel.setMonitor("三星的显示器");
return new BananaComputer(computerModel);
}
}
public class DellComputer11Factory implements ComputerFactory {
@Override
public Computer create() {
ComputerModel computerModel = new ComputerModel();
computerModel.setType("戴尔一代");
computerModel.setMainEngine("dell的主机");
computerModel.setMonitor("dell的显示器");
return new DellComputer(computerModel);
}
}
测试
public class MethodFactoryTest {
public static void main(String[] args) {
ComputerFactory factory = new BananaComputer01Factory();
Computer banana = factory.create();
System.out.println(banana.getBrand());
System.out.println(banana.getComputerModel().toString());
System.out.println("------------------------------------");
factory = new DellComputer11Factory();
Computer dell = factory.create();
System.out.println(dell.getBrand());
System.out.println(dell.getComputerModel().toString());
}
}
输出
香蕉
ComputerModel(type=香蕉一代, mainEngine=联想的主机, monitor=三星的显示器)
------------------------------------
戴尔
ComputerModel(type=戴尔一代, mainEngine=dell的主机, monitor=dell的显示器)
我们再看一下UML类图
通过上面的例子可以看到ComputerFactory抽象成接口之后不再承担创建对象的职责,而是由每个品牌的工厂去实现创建该品牌的电脑的工作。如果业务变动需要加新的产品或品牌则创建相应的品牌工厂实现ComputerFactory,再创建相应的电脑类即可,符合了开闭原则。但是工厂方法在某些情况下还是缺乏一些扩展性的,比如业务变动,为了拥抱智能大时代,所有的工厂都要支持生产手机,空调等设备,这个时候可以考虑抽象工厂模式。
3.3 抽象工厂
抽象工厂模式(Abastract Factory Pattern)是指提供一个创建一系列相关或相互依赖对象的接口,无须指定他们具体的类。客户端(应用层)不依赖于产品类实例如何被创建、实现等细节,强调的是一系列相关的产品对象(属于同一产品族)一起使用创建对象需要大量重复的代码。需要提供一个产品类的库,所有的产品以同样的接口出现,从而使客户端不依赖于具体实现。
现在我们的工厂要支持生产手机了
创建手机抽象类
public interface Phone {
void getName();
}
创建智能设备工厂抽象类
public interface SmartDeviceFactory {
/** 生产电脑 */
Computer createComputer();
/** 生产手机 */
Phone createPhone();
}
创建香蕉牌手机类
public class BananaPhone implements Phone{
@Override
public void getName() {
System.out.println("-----香蕉手机------");
}
}
创建戴尔牌手机类
public class DellPhone implements Phone{
@Override
public void getName() {
System.out.println("-----戴尔手机------");
}
}
创建香蕉工厂
public class BananaFactory implements SmartDeviceFactory {
@Override
public Computer createComputer() {
ComputerModel computerModel = new ComputerModel();
computerModel.setType("香蕉一代");
computerModel.setMainEngine("联想的主机");
computerModel.setMonitor("三星的显示器");
return new BananaComputer(computerModel);
}
@Override
public Phone createPhone() {
return new BananaPhone();
}
}
创建戴尔工厂
public class DellFactory implements SmartDeviceFactory {
@Override
public Computer createComputer() {
ComputerModel computerModel = new ComputerModel();
computerModel.setType("戴尔一代");
computerModel.setMainEngine("dell的主机");
computerModel.setMonitor("dell的显示器");
return new DellComputer(computerModel);
}
@Override
public Phone createPhone() {
return new DellPhone();
}
}
测试
public class AbstractFactoryTest {
public static void main(String[] args) {
SmartDeviceFactory factory = new BananaFactory();
Computer banana = factory.createComputer();
System.out.println(banana.getBrand());
System.out.println(banana.getComputerModel().toString());
factory.createPhone().getName();
System.out.println("------------------------------------");
factory = new DellFactory();
Computer dell = factory.createComputer();
System.out.println(dell.getBrand());
System.out.println(dell.getComputerModel().toString());
factory.createPhone().getName();
}
}
输出
香蕉
ComputerModel(type=香蕉一代, mainEngine=联想的主机, monitor=三星的显示器)
-----香蕉手机------
------------------------------------
戴尔
ComputerModel(type=戴尔一代, mainEngine=dell的主机, monitor=dell的显示器)
-----戴尔手机------
我们再看一下UML类图
精简一些,只看一个分支的UML类图
从上面的例子可以看出来,抽象工厂与工厂方法其实是很类似的。我认为主要的区别在于工厂的职责是否单一,工厂方法职责单一符合开闭原则,而抽象工厂则是定义了一类产品族的多项职责,支持扩展,但是在扩展时需要在抽象工厂和实体工厂都要加上相应的创建方法,这是违背了开闭原则的。
到这里,基本上工厂模式的三个分类就分析完了。在实际业务中,应该根据实际业务的复杂程度,是否频繁升级等因素来选取使用哪种工厂模式。当然,在使用时也不需要拘泥于形式,最重要的是这个模式的思想,适合自己项目的才是最好的!