引言
首先我们由一个实际问题来引出抽象工厂模式。
考虑这样一个场景,系统中需要向OSS上传文件,以及通过OSS下载文件。而在系统中有不同的业务在使用这两个功能。如下图:

抽象工厂模式引言类图
伪代码如下
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 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90
| public interface FileUploader {
String uploadFile(File file); }
public interface FileDownloader {
InputStream download(String path); }
@Slf4j public class AliyunFileUploaderImpl implements FileUploader {
@Override public String uploadFile(File file) { log.info("向阿里云OSS上传文件"); return "/test/" + file.getName(); } }
@Slf4j public class AliyunFileDownloaderImpl implements FileDownloader { @Override public InputStream download(String path) { log.info("通过阿里云下载文件"); return ByteArrayInputStream.nullInputStream(); } }
@Slf4j public class XxxService1 {
private final FileUploader fileUploader = new AliyunFileUploaderImpl();
public void doService(File file) { String filePath = this.fileUploader.uploadFile(file); log.info("文件上传到了:{}", filePath); log.info("XxxService1 执行其他业务代码"); } }
@Slf4j public class XxxService2 {
private final FileUploader fileUploader = new AliyunFileUploaderImpl();
private final FileDownloader fileDownloader = new AliyunFileDownloaderImpl();
public void doService1(File file) { String filePath = this.fileUploader.uploadFile(file); log.info("文件上传到了:{}", filePath); log.info("XxxService2 执行doService1 业务代码"); }
public void doService2(String path) { InputStream file = this.fileDownloader.download(path); log.info("文件下载了文件:{}", path); log.info("XxxService2 执行doService2 业务代码"); } }
public class Main {
public static void main(String[] args) {
XxxService1 service1 = new XxxService1(); File file = Mockito.mock(File.class); service1.doService(file);
XxxService2 service2 = new XxxService2(); File file2 = Mockito.mock(File.class); service2.doService1(file2);
service2.doService2("xxx"); } }
|
这时另外一个客户也需要用这套系统,但是客户不想用阿里云的OSS存储文件,想用华为云或者Minio存储文件。
那问题就来了,我们需要新的FileUploader
和FileDownloader
实现,并且在所有代码中替换掉原来的 AliyunFileUploaderImpl
和 AliyunFileDownloaderImpl
配置。而且我们需要修改每一个使用它们的类。这样很麻烦,会使我们的代码可维护性变得很差。而且漏改也会出现系统性BUG。
FileUploader
和FileDownloader
的创建必定是要依赖一些具体配置的,如阿里云的 accessKeyId
和 accessKeySecret
。不同的实现,配置也不同。这也是我建议需要通过工厂来创建它们的一个原因。但是在下文中并没有添加相关的配置依赖,在实际的业务中,这些都是必不可少的。请各位同学注意。
下面我们通过抽象工厂模式来解决上述问题。
有没有发现上面的代码中违背了哪个设计原则呢?
上面的代码违背了依赖倒置原则。我们虽然定义的字段类型是 FileUploader
但是我们直接在业务类中实例化了具体类。这样等于间接的与具体类发生了耦合。正确的做法应该是通过 setter
或构造函数将具体类注入到业务类的实例中。
当然直接实例化具体类的做法在很多框架中也是很常见的,这种做法并不是完全不可取。但是一般这样做的目的是简化类实例化时的配置,直接提供依赖接口的默认实现,并提供 serter
方法可以替换掉默认实现。
注意
这个例子并不是很恰当,因为现在其实都在用Spring框架。如果将 FileUploader
和FileDownloader
改为依赖注入的方式,就不会有这样的问题。通过Spring的依赖注入能够很好的解决这个问题是因为Spring本身就是一个大的工厂。
另外不要认为工厂就必须要产生新的对象,这样想是不正确的。工厂只是把你需要的对象给你,至于是新创建对象还是使用已实例化的对象,根据业务场景去处理即可。
定义及实现
定义
首先我们了解下抽象工厂模式的定义。
Provide an interface for creating families of related or dependent objects without specifying their concrete classes.
为创建一组相关或相互依赖的对象提供一个接口, 而且无须指定它们的具体类。
这个定义不太容易理解,我们通过UML类图和具体的代码来逐步理解其含义。
结构

抽象工厂模式类图
我们对上面的结构做下调整,让业务类依赖 AbstractFileManagerFactory
,不再直接依赖FileUploader
和FileDownloader
。而FileUploader
和FileDownloader
的创建则放到抽象工厂的具体实现类中来进行。而业务类中也不再关心抽象工厂的具体实现,只依赖抽象工厂接口。具体实现则通过 setter
或构造函数注入。
上面定义中提到的 “为创建一组相关或相互依赖的对象提供一个接口” 就是抽象工厂。对于我们的这个业务场景来说文件的上传、下载就是一组相关的对象。
- 抽象工厂并并不是必须定义为抽象类,可以是抽象类或接口。
代码实现
原来的 FileUploader
和 FileDownloader
以及 AliyunFileUploaderImpl
和 AliyunFileDownloaderImpl
不动,我们先增加 AbstractFileManagerFactory
和 AliyunFileManagerFactory
并修改两个Service。先实现其中一个,然后再实现另一个,看看如何替换,以体会抽象工厂模式解决了什么问题。
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 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56
| public interface AbstractFileManagerFactory {
FileUploader getFileUploader();
FileDownloader getFileDownloader(); }
public class AliyunFileManagerFactory implements AbstractFileManagerFactory {
private final FileUploader fileUploader;
private final FileDownloader fileDownloader;
public AliyunFileManagerFactory() { this.fileUploader = new AliyunFileUploaderImpl(); this.fileDownloader = new AliyunFileDownloaderImpl(); }
@Override public FileUploader getFileUploader() { return this.fileUploader; }
@Override public FileDownloader getFileDownloader() { return this.fileDownloader; } }
@Slf4j public class XxxService1 {
private final AbstractFileManagerFactory factory;
public XxxService1(AbstractFileManagerFactory factory) { this.factory = factory; }
public void doService(File file) { String filePath = this.factory.getFileUploader().uploadFile(file); log.info("文件上传到了:{}", filePath); log.info("XxxService1 执行其他业务代码"); } }
public class Main {
public static void main(String[] args) {
AbstractFileManagerFactory factory = new AliyunFileManagerFactory();
XxxService1 service1 = new XxxService1(factory); File file = Mockito.mock(File.class); service1.doService(file); } }
|
这时候我们再实现一套Minio的逻辑,看看如何替换成Minio
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 28 29 30 31 32 33 34 35 36 37 38 39 40 41
| @Slf4j public class MinioFileUploaderImpl implements FileUploader {
@Override public String uploadFile(File file) { log.info("向minio上传文件"); return "/test/" + file.getName(); } }
@Slf4j public class MinioFileDownloaderImpl implements FileDownloader { @Override public InputStream download(String path) { log.info("通过minio下载文件"); return ByteArrayInputStream.nullInputStream(); } }
public class MinioFileManagerFactory implements AbstractFileManagerFactory {
private final FileUploader fileUploader;
private final FileDownloader fileDownloader;
public MinioFileManagerFactory() { this.fileUploader = new MinioFileUploaderImpl(); this.fileDownloader = new MinioFileDownloaderImpl(); }
@Override public FileUploader getFileUploader() { return this.fileUploader; }
@Override public FileDownloader getFileDownloader() { return this.fileDownloader; } }
|
Service 不用动,修改下入口程序。
1 2 3 4 5 6 7 8 9 10 11 12
| public class Main {
public static void main(String[] args) {
AbstractFileManagerFactory factory = new MinioFileManagerFactory();
XxxService1 service1 = new XxxService1(factory); File file = Mockito.mock(File.class); service1.doService(file); } }
|
抽象工厂针对的是一组或一系列相关或相互依赖的接口,而工厂方法和简单工厂则是针对的单一的接口。这一点区别大家要注意。如果只针对一个接口应该根据场景选择简单工厂或工厂方法。
抽象工厂模式有什么问题
从上面的例子中我们可以看出来,抽象工厂的弊端也很大。虽然我们不再依赖FileUploader
和FileDownloader
的具体实现,改为依赖了 AbstractFileManagerFactory
,但是只要我们增加新的文件上传和下载方式,就需要在业务类创建的地方全部修改一遍。这不符合开闭原则。有没有什么方法能解决这个问题呢?当然是有的。
- 通过Spring的依赖注入来解决,业务类依赖
AbstractFileManagerFactory
,而我们使用哪个具体实现就将其创建(或注册)为Spring Bean。让Spring来管理类之间的依赖关系。
- 通过简单工厂来处理。业务层依赖简单工厂,简单工厂通过读取配置决定创建抽象工厂的哪个具体实现。这样修改代码只需要修改简单工厂类即可,无需对业务类做大面积修改。

简单工厂+抽象工厂
使用简单工厂处理的源码清单如下:
首先需要定义一个配置,用来指定要使用的工厂类。配置文件放在 src/main/resources/config.properties
1
| factory=org.depsea.designpattern.creation.afp.e02.factory.MinioFileManagerFactory
|
下面是 SimpleFileManagerFactory
的具体实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| public class SimpleFileManagerFactory {
private final AbstractFileManagerFactory factory;
public SimpleFileManagerFactory() throws Exception { Class<? extends AbstractFileManagerFactory> factoryClass = this.getFactoryClass(); this.factory = factoryClass.getConstructor().newInstance(); }
public AbstractFileManagerFactory getFactory() { return factory; }
@SuppressWarnings("unchecked") private Class<? extends AbstractFileManagerFactory> getFactoryClass() throws IOException, ClassNotFoundException { Properties properties = new Properties(); properties.load(getClass().getClassLoader().getResourceAsStream("config.properties")); String clazz = properties.getProperty("factory"); return (Class<? extends AbstractFileManagerFactory>) Class.forName(clazz); } }
|
入口程序则修改为
1 2 3 4 5 6 7 8 9 10 11 12
| public class Main {
public static void main(String[] args) throws Exception {
SimpleFileManagerFactory simpleFileManagerFactory = new SimpleFileManagerFactory(); AbstractFileManagerFactory factory = simpleFileManagerFactory.getFactory();
XxxService1 service1 = new XxxService1(factory); File file = Mockito.mock(File.class); service1.doService(file); } }
|
至此添加新的文件上传下载支持,只需要修改配置文件即可,无需对业务代码甚至是工厂做任何的修改。
上面的代码同样有弊端,在通过反射做实例化的时候,使用的是默认构造函数,如果工厂没有默认构造函数,则会出现无法实例化的情况,这限制了代码的灵活性。
实际应用
Spring中的 BeanFactory
就是抽象工厂模式的最佳应用。

抽象工厂的实际应用
而Spring给我们提供了一种新的思路,通过不同的方式来创建同一个对象。比如上面的例子中 ClassPathXmlApplicationContext
提供的是一种通过XML来创建Bean的方式;而AnnotationConfigApplicationContext
则提供通过注解来创建Bean的方式。
而在一个项目中可以同时支持两种Bean创建方式,这不是本篇文章的重点,有兴趣的同学可以通过学习Spring源码来了解其工作原理。
BeanFactory
是抽象工厂模式,创建了一系列的Bean。
FactoryBean
是工厂方法模式,只创建单一Bean。
请注意以上两者的区别。
总结
抽象工厂模式是一种创建型设计模式,它提供一个接口用于创建一系列相关或依赖对象的家族,而不需要指定具体的类。这有助于实现对象的解耦和灵活性。
适用于需要创建一系列相关对象的场景。它可以提高系统的可维护性和可扩展性。
优点:
- 实现了对象的解耦:客户端使用抽象工厂接口来创建产品对象,而无需关心具体的产品类,从而实现了对象的解耦。
- 易于替换产品系列:由于客户端使用抽象工厂接口来创建产品对象,因此可以轻松地替换整个产品系列,而不需要修改客户端代码。
缺点
- 不够灵活:一旦需要添加新的产品类,就需要修改抽象工厂的接口和所有的具体工厂,这可能会带来一定的工作量。