工厂模式是为了把对象的内容耦合进行解耦,变成一种更好的数据耦合的方式,最终的目的是还是为了解耦。
工厂模式的定义是:定义一个用于创建对象的接口,然后让子类决定实例化哪一个类。这样的目的是让一个类的实例化延迟到子类中完成

工厂模式的UML类图如下:

image.png

举一个实际中应用的例子,比如我们的程序中需要一个日志工具,记录日志的方式有很多种,可能初期的时候我们可能只是在控制台打印日志,后续系统正式上线后,我们使用文件的方式。根据系统的规划后续随着分析日志的需求,我们可能会增加基于Redis或者MangoDB方式将日志保存到数据库中。我们还希望可以通过配置的方式切换日志工具。这是就需要用到工厂方法。

为了实现对象的延迟实例化,我们首先需要定义一个接口,它是具体要创建实例的抽象,比如创建一个日志对象的接口:

1
2
3
4
public interface Logger {
void log(String message);
}

这个接口中我们定义了log方法,这是所有日志对象都要实现的方法。假设我们现在要提供一个控制台打印的日志对象,那么可以这样实现:

1
2
3
4
5
6
7
public class ConsoleLogger implements Logger {
@Override
public void log(String message) {
System.out.println("ConsoleLogger: " + message);
}
}

现在,我们要再实现一个向文件写入日志的对象FileLogger

1
2
3
4
5
6
7
8
public class FileLogger implements Logger {

@Override
public void log(String message) {
// 模拟将日志写入文件的代码
System.out.println("FileLogger: " + message);
}
}

以及一个向Redis写入日志的对象 RedisLogger

1
2
3
4
5
6
7
8
public class RedisLogger implements Logger {
@Override
public void log(String message) {
// 模拟将日志写入Redis
System.out.println("RedisLogger: " + message);
}
}

刚才我们说过,要将对象的创建延迟到子类中,那么就需要一个创建对象的工厂方法:

1
2
3
4
5
6
7
8
9
10
11
12
public class LoggerFactory {
public static Logger getLogger(String type) {
if (type.equals("console")) {
return new ConsoleLogger();
} else if (type.equals("file")) {
return new FileLogger();
} else if (type.equals("redis")) {
return new RedisLogger();
}
return null;
}
}

如上面的代码所示,我们根据type参数来判定要创建的日志工具是什么类型的,这样就降低了使用日志工具的类的耦合性。下面是一个具体调用工厂方法的例子:

1
2
3
4
5
6
public class FactoryClient {
public static void main(String[] args) {
Logger logger = LoggerFactory.getLogger("console");
logger.log("这是一条日志消息");
}
}

上面这种方式是工厂模式中的简单工厂,我们只是通过参数判定来决定创建哪个类。如果再进一步可以实现一个抽象工厂,它是进一步将工厂对象抽象为一个接口,然后不同的产品对象由不同的工厂对象来创建,这种方式避免了在需要新增产品时修改工厂对象中创建方法,更加灵活,但是相对而言实现方式较复杂,但是灵活性更高。
下面是抽象工厂的UML类图
image.png
具体抽象工厂的示例代码这里就不提供了。总结一下,工厂方法这个设计模式,更符合面向对象中的“符合开闭原则、单一职责原则”的设计原则。并且很好的实现了对象创建的解耦。比如上面的抽象工厂中,如果要增加一种产品,只要继承FactoryProduct 这两个接口就可以实现,无需对现有代码进行更改。但是它在一定程度上增加了复杂性,因为每增加一个具体的产品就要增加一组类,代理维护成本高了,如果是具体产品的种类不多或者变化不频繁使用抽象工厂方法会稍显复杂,一般用简单工厂方法即可。