抽象工厂模式
上一篇文章我们介绍了 工厂方法模式,并用一个 ILogger
的例子展示了工厂方法模式的核心思想:暴露一个抽象的工厂方法让子类来实现,子类就可以根据自身的需求来创建不同的对象。
抽象工厂模式(Abstract Factory Pattern)也包含「工厂」的概念,但是代码实现上和工厂方法模式有较大的区别。
工厂方法模式主要是依赖子类继承并实现抽象方法来实现,而抽象工厂模式则是通过构造函数传入一个工厂类来创建一系列对象。
以 ILogger
的例子对比讲解一下就明白了:
工厂方法模式 中,我们实现了一个 Application
类,并暴露了一个抽象方法 createLogger()
,我们实现了三个 Application
子类,分别实现 createLogger()
方法即可定制自己的日志记录器。
如果用本文介绍的抽象工厂模式来实现,需要创建一个抽象工厂接口:
interface ILoggerFactory {
ILogger createLogger();
}
然后实现三个具体的工厂类,每个工厂类都创建一种不同的日志记录器:
// 控制台日志具体工厂
class ConsoleLoggerFactory implements ILoggerFactory {
@Override
public ILogger createLogger() {
return new ConsoleLogger();
}
}
// 文件日志具体工厂
class FileLoggerFactory implements ILoggerFactory {
@Override
public ILogger createLogger() {
return new FileLogger();
}
}
// 远程日志具体工厂
class RemoteLoggerFactory implements ILoggerFactory {
@Override
public ILogger createLogger() {
return new RemoteLogger();
}
}
日志接口 ILogger
及其实现类 ConsoleLogger, FileLogger, RemoteLogger
和 工厂方法模式 中的实现完全相同,这里不再重复。
最后,可以这样实现我们的应用程序,只需要在构造函数中传入一个具体工厂类即可:
class Application {
private ILogger logger;
public Application(ILoggerFactory loggerFactory) {
// 通过工厂创建日志记录器
this.logger = loggerFactory.createLogger();
}
public void doSomething() {
logger.log("Start doing something...");
}
public static void main(String[] args) {
// 根据环境选择不同的工厂
ILoggerFactory loggerFactory;
String env = System.getenv("ENV");
if (env.equals("dev")) {
loggerFactory = new ConsoleLoggerFactory();
} else if (env.equals("test")) {
loggerFactory = new FileLoggerFactory();
} else if (env.equals("prod")) {
loggerFactory = new RemoteLoggerFactory();
} else {
throw new IllegalArgumentException("Invalid environment: " + env);
}
// 将工厂传入应用程序
Application app = new Application(loggerFactory);
app.doSomething();
}
}
上面这个简单的例子就展示了抽象工厂模式,主要有以下几个关键角色:
- 抽象产品(AbstractProduct):定义了抽象工厂所创建的对象的接口,即
ILogger
接口。 - 具体产品(ConcreteProduct):实现了抽象产品接口的具体对象,即
ConsoleLogger
、FileLogger
和RemoteLogger
类。 - 抽象工厂(AbstractFactory):抽象工厂接口,即
ILoggerFactory
接口,声明了创建方法。 - 具体工厂(ConcreteFactory):实现抽象工厂接口的具体工厂类,即
ConsoleLoggerFactory
、FileLoggerFactory
和RemoteLoggerFactory
类。
不过上述示例中,抽象工厂只有一个 createLogger
方法,过于简单了,这个场景用抽象工厂模式有些大材小用。
在实际场景中,抽象工厂通常会包含多个创建方法,用于创建一系列「相关的」对象,下面举例说明。
场景案例
考虑这样一个实际场景:我们要开发一个应用程序,其中包含 IButton
, ICheckbox
, IText
等 UI 组件,这些组件需要支持不同的主题风格,比如亮色/暗色。如果切换为亮色主题,那么所有 UI 组件都必须是亮色主题的,反之亦然。
我们可以创建一个抽象工厂接口 IThemeFactory
,包含多个创建方法,用于创建不同的 UI 组件:
// 抽象工厂接口,能够创建多种 UI 组件
interface IThemeFactory {
IButton createButton();
ICheckbox createCheckbox();
IText createText();
}
// 抽象产品接口,即多种 UI 组件接口
interface IButton {
void render();
}
interface ICheckbox {
void render();
}
interface IText {
void render();
}
然后实现两个具体工厂类,每个具体工厂类都创建一套 UI 组件:
// 亮色主题具体工厂
class LightThemeFactory implements IThemeFactory {
@Override
public IButton createButton() {
return new LightButton();
}
@Override
public ICheckbox createCheckbox() {
return new LightCheckbox();
}
@Override
public IText createText() {
return new LightText();
}
}
// 具体产品,即亮色主题下的 UI 组件类
class LightButton implements IButton {
@Override
public void render() {
System.out.println("Render Light Button");
}
}
class LightCheckbox implements ICheckbox {
@Override
public void render() {
System.out.println("Render Light Checkbox");
}
}
class LightText implements IText {
@Override
public void render() {
System.out.println("Render Light Text");
}
}
// 暗色主题具体工厂
class DarkThemeFactory implements IThemeFactory {
@Override
public IButton createButton() {
return new DarkButton();
}
@Override
public ICheckbox createCheckbox() {
return new DarkCheckbox();
}
@Override
public IText createText() {
return new DarkText();
}
}
// 具体产品,即暗色主题下的 UI 组件类
class DarkButton implements IButton {
@Override
public void render() {
System.out.println("Render Dark Button");
}
}
class DarkCheckbox implements ICheckbox {
@Override
public void render() {
System.out.println("Render Dark Checkbox");
}
}
class DarkText implements IText {
@Override
public void render() {
System.out.println("Render Dark Text");
}
}
最后,可以这样实现我们的应用程序,只需要在构造函数中传入一个具体工厂类即可:
class Application {
private IButton button;
private ICheckbox checkbox;
private IText text;
public Application(IThemeFactory ThemeFactory) {
this.button = ThemeFactory.createButton();
this.checkbox = ThemeFactory.createCheckbox();
this.text = ThemeFactory.createText();
}
public void start() {
button.render();
checkbox.render();
text.render();
}
public static void main(String[] args) {
Application darkApp = new Application(new DarkThemeFactory());
darkApp.start();
// Render Dark Button
// Render Dark Checkbox
// Render Dark Text
Application lightApp = new Application(new LightThemeFactory());
lightApp.start();
// Render Light Button
// Render Light Checkbox
// Render Light Text
}
}
如果需要实现其他主题,只需要新创建一个实现 IThemeFactory
接口的具体工厂类,并创建相应的 UI 组件即可,Application
应用程序中的代码不需要修改。
总结
抽象工厂模式的主要优势:
1、分离具体类,降低耦合度。从上面的示例可以看出,应用程序只依赖抽象工厂接口和抽象产品接口,不会依赖具体工厂和具体产品,这样就可以在不修改业务逻辑的情况下,轻松切换具体产品类,降低了耦合度。
2、保证产品族的兼容性。一个具体工厂会负责创建一系列具体产品,可以保证这些具体产品是互相兼容的。比如暗色主题工厂中创建的 UI 组件都是暗色主题的,这样能保证应用程序的主题统一。
因为需要创建很多类和接口,所以抽象工厂的主要缺点是增加了代码的复杂性。
一般情况下,只有比较复杂的业务场景可以考虑将通用代码抽取出来使用抽象工厂模式,对于简单的业务场景,使用抽象工厂模式反而更难以维护,得不偿失。