在项目的开发中,我们经常会遇到需要创建复杂对象的场景,例如一个参数繁多的类的构造函数。这时如果我们不使用任何设计模式进行开发,项目将变得十分复杂,面对常常变动的需求,我们可能需要一个一个修改参数,并在任何引用该类的地方都做一次修改,最后只会变得乱成一团无法维护。那么该如何解决这个问题呢?
答案是使用工厂模式。在需要创建复杂对象,并频繁使用时,我们就可以选择工厂模式。
工厂模式的三种实现形式
工厂模式有三种实现形式
- 简单工厂(静态工厂)
- 工厂方法(本节核心)
- 抽象工厂
注:简单工厂并不算在GoF 23种设计模式中
本节,我们将深入理解工厂方法模式及其应用。
简单工厂(静态工厂)
在介绍工厂方法模式之前,我先对简单工厂进行介绍。在简单工厂模式中,创建实例的方法通常为静态方法,因此简单工厂模式又被成为静态工厂方法。简单工厂只有一个具体的工厂类,专门用于生产特定专一的产品,在需要生产多种产品时,需要将多个产品定义到同一个工厂中进行生产。简单工厂名副其实,十分简单。这样的好处就是产品创建方便,客户直接提需求,工厂直接实现,不需要经过任何产品线。但问题也来了,在需要大量创建不同产品时,简单工厂就会变得“不堪重负”。回归到软件中,简单工厂会增加系统的复杂性,在多个产品的情况下,单个类职责过重,软件将变得难以维护。每增加一个产品就要增加一个工厂类,违背了“开闭原则”。
简单工厂的示例
我们有苹果树和梨树和一家工厂,这家工厂要负责将水果树上的所有水果都摘下来。
package com.yeliheng.factory.simplefactory;
public class Main {
public static void main(String[] args) {
Apple apple = (Apple) SimpleFactory.make(0);
Banana banana = (Banana) SimpleFactory.make(1);
apple.show();
banana.show();
}
//水果产品
public interface Fruit {
void show();
}
//具体产品
static class Apple implements Fruit {
public void show() {
System.out.println("苹果树上长苹果...");
}
}
//具体产品
static class Banana implements Fruit {
public void show() {
System.out.println("香蕉树上长香蕉...");
}
}
//工厂生产
static class SimpleFactory {
public static Fruit make(int kind) {
switch (kind) {
case 0:
return new Apple();
case 1:
return new Banana();
}
return null;
}
}
}
通过代码示例我们可以看到每生产一种具体的水果,就要定义一个新的静态产品类让水果能被工厂生产。工厂类的代码将会随着产品的增加越来越复杂。
为了解决这个问题,工厂方法(Factory Method)模式出现了。
工厂方法
工厂方法模式在简单工厂模式的基础上进一步抽象,可以使系统拓展新的具体类时,原来的工厂代码不必修改,满足开闭原则。拓展新的需求,创建新产品,只需要在原来的基础上增加一个工厂类,这样就可以有多个工厂为我们服务。并且上层应用只需了解抽象的产品类,无需对其他类进行拓展。这也同时满足了迪米特法则、依赖倒置原则和里氏替换原则。但工厂方法模式同样存在缺点。它将会让工厂类数目繁多,降低系统代码的可读性。并且在抽象产品中,只能产出一种具体产品。(这个问题可以使用抽象工厂解决,下节介绍)。
工厂方法模式由以下结构组成:
- 抽象工厂(AbstractFactory):提供了产品的抽象类或接口。
- 具体工厂(ConcreteFactory):具体的创建类,实现具体生产产品的方法。
- 抽象产品(Product):产品的抽象创建类。
- 具体产品(ConcreteProduct):将抽象产品进行实现。
实际应用场景
- 日志工厂
- 文件存储
…
示例
我们来实现一个日志工厂,该日志工厂具有以下三个功能:
- 打印日志到控制台
- 打印日志到文件
我们按照上文所述的结构一一实现工厂方法:
- 抽象产品
Logger.java
package com.yeliheng.factory.factorymethod;
/**
* 日志产品的抽象
*/
public interface Logger {
//日志级别
void debug();
void info();
void warning();
void error();
}
我们在抽象产品中定义了四个产品接口,可对应四种不同级别的日志。
- 抽象工厂
LoggerFactory.java
package com.yeliheng.factory.factorymethod;
public interface LoggerFactory {
Logger getLogger();
}
在抽象工厂中,我们去使用Logger,定义一个getLogger()方法来便于待会具体工厂获取Logger实例。
- 具体产品
我们实现两个具体产品类,一个是输出到控制台的产品类,一个是输出到文件的产品类。这里为了代码的简洁,就不详细地写出输出到文件的逻辑,仅使用输出进行简单举例。
ConsoleLogger.java
package com.yeliheng.factory.factorymethod;
public class ConsoleLogger implements Logger{
@Override
public void debug() {
}
@Override
public void info() {
System.out.println("[INFO] 这条日志被输出到[控制台]");
}
@Override
public void warning() {
}
@Override
public void error() {
}
}
FileLogger.java
package com.yeliheng.factory.factorymethod;
public class FileLogger implements Logger{
@Override
public void debug() {
}
@Override
public void info() {
System.out.println("[INFO] 这条日志被输出到[文件]");
}
@Override
public void warning() {
}
@Override
public void error() {
}
}
- 实现具体的工厂
ConsoleLoggerFactory.java
package com.yeliheng.factory.factorymethod;
/**
* 具体工厂
*/
public class ConsoleLoggerFactory implements LoggerFactory{
@Override
public Logger getLogger() {
return new ConsoleLogger();
}
}
我们重写了实现了抽象工厂:LoggerFactory,并重写了getLogger方法,让其返回一个ConsoleLogger实例。
FileLoggerFactory.java
package com.yeliheng.factory.factorymethod;
public class FileLoggerFactory implements LoggerFactory{
@Override
public Logger getLogger() {
return new FileLogger();
}
}
原理相同。
- 最后,我们可以开始使用我们通过工厂方法模式创建的日志工厂了。
Main.java
package com.yeliheng.factory.factorymethod;
public class Main {
public static void main(String[] args) {
//输出到控制台
LoggerFactory consoleLoggerFactory = new ConsoleLoggerFactory();
Logger logger1 = consoleLoggerFactory.getLogger();
logger1.info();
//输出到文件
LoggerFactory fileLoggerFactory = new FileLoggerFactory();
Logger logger2 = fileLoggerFactory.getLogger();
logger2.info();
}
}
可以看到最终输出如下:
细心的你可能会发现,这个写法与slf4j日志库的写法十分相似。没错,查看slf4j源码可以发现,其使用了大量工厂模式实现强大的日志框架。
小结
到此,我们使用工厂方法模式完成了一个简单的日志工厂,下节我们将使用抽象方法模式继续完善本节的例子。
源码参考:Github