设计模式: 工厂模式
工厂是每一个开发人员应该知道的关键创造模式之一。它们是许多高级模式的主要组成部分。很长一段时间,我对不同类型的工厂模式感到困扰。此外,在同一篇文章中很难找到关于这些类型的信息。本文介绍了四种工厂模式:
- 工厂方法模式
- 抽象工厂模式
- 静态工厂方法
- 简易工厂
“四人帮”在《设计模式: 可复用面向对象软件的基础》一书中对工厂方法模式进行了描述。当我第一次读到这个模式时,我用静态模式误解了它,这是由 Java api 的主要设计师之一 Joshua Bloch 在他的书“ Effective Java”中描述的。这个简单的工厂(有时称为工厂)是非正式的,但是在网上出现了很多次。最后一种是抽象的工厂模式,在《四人帮》一书中也有描述,它是工厂方法模式的更广泛概念。
在这篇文章中,我将解释工厂有什么用,然后我将用著名的Java框架或Java API的示例介绍每种类型。我将使用Java代码来实现工厂,但是如果您不了解Java,您仍然可以理解这个想法。此外,我将使用UML正式描述模式。
反面教材
尽管本文是关于工厂模式的,但仅仅为了使用模式而使用模式比从来不使用模式更糟糕。这种行为是一种反模式。实际上,大多数模式使代码更难理解。大多数时候,我不用工厂。例如:
- 不会改变很多的小项目
- 涉及多个开发人员使用相同代码的大中型项目,我发现它们很有用
我认为工厂模式是在它们的优势[^将在下一部分中看到它们]以及代码的可读性和理解性之间的权衡。
工厂的主要目标是实现对象,但是为什么不直接用构造函数调用创建对象呢?
对于简单的用例,不需要使用工厂。
@Data
@AllArgsConstructor
public class Simple {
private final Integer parameter1;
private final Integer parameter2;
}
public class SimpleFactory {
/**
* 用来创建对象的工厂方法
*/
public static Simple createFactory() {
return new Simple(1, 2);
}
public static void main(String[] args) {
// 得到工厂创建的对象
System.out.println(createFactory());
// TODO 你的业务代码
}
}
在这段代码中,SimpleClass 是一个非常简单的类,具有状态、没有依赖性、没有多态性、没有业务逻辑。您可以使用工厂来创建这个对象,但是它将使代码量翻倍。因此,这会使代码更难理解。如果你能避免使用工厂来做这件事,你最终会得到一个更简单的代码!
但是,在编写需要许多开发人员和许多代码更改的大型应用程序时,您经常会遇到更复杂的情况。对于这些复杂的情况,工厂的优势取代
了它们的劣势。
对工厂的需求
既然我已经警告过你工厂的用途,让我们来看看为什么它们如此强大,因此在大多数项目中得到了应用。
静态工厂
企业级应用程序的一个常见用例是限制一个类的数量。如何设法只拥有一个(或2个或10个)类实例,因为它消耗了诸如套接字、数据库连接或文件系统描述符等资源?
使用构造函数的方法,不同的函数(来自不同的类)很难知道一个类的实例是否已经存在。而且,即使有一个实例,一个函数怎么能得到这个实例呢?你可以通过使用每个函数都会检查的共享变量来做到这一点
- 它将链接所有的函数的行为,因为它们使用和修改相同的共享变量,所以需要在同一个类中实现。
- 代码的多个部分将具有相同的逻辑来检查类是否已经被即时化,这将导致代码重复(非常糟糕!)
使用静态工厂方法,你可以很容易做到这一点:
public class Singleton {
private static final Singleton INSTANCE = new Singleton();
private Singleton(){}
public static Singleton getInstance(){
return INSTANCE;
}
}
public class SingletonTest1{
public static void getInstance(){
// 获取实例对象
Singleton instance = Singleton.getInstance();
// TODO 其他业务代码
}
}
public class SingletonTest2{
public static void getInstance(){
// 获取实例对象
Singleton instance = Singleton.getInstance();
// TODO 其他业务代码
}
}
在这段代码中,我们使用的工厂将 Singleton 类的实例数限制为一个。通过限制对象的数量,我们创建了一个实例池,这个实例池基于一个工厂。
注意: 我们可以修改一个实例的创建方式,而不是限制数量,例如使用一个原型模式,而不是每次都从头创建一个新对象。
松散耦合
工厂的另一个优点是松耦合。
假设您编写了一个计算东西并需要写日志的程序。由于这是一个大项目,所以在编写业务类时,您的同事之后会对将日志写入文件系统(FileSystemLogger 类)的类进行编码。在没有工厂的情况下,在使用 FileSystemLogger 之前,你需要在一个构造函数中注明 FileSystemLogger:
public class FileSystemLogger {
public void writeLog(String s) {
System.out.println("日志信息为:" + s);
// TODO 其他业务代码
}
public static void main(String[] args) {
FileSystemLogger logger = new FileSystemLogger();
logger.writeLog("这是一个日志");
}
}
但是,如果突然发生变化,您现在需要使用实现的 DatabaseLogger 在数据库中写入日志,那会发生什么呢?如果没有工厂,则必须使用 FileSystemLogger类修改所有函数。因为这个记录器无处不在,你需要修改数百个函数/类,而使用工厂,你只需修改工厂就可以轻松地从一个实现切换到另一个实现:
public interface Logger {
/**
* 输出日志
*
* @param s 日志信息
*/
void writeLog(String s);
}
/**
* 系统日志
*/
class SystemLogger implements Logger {
@Override
public void writeLog(String s) {
System.out.println("系统日志" + s);
}
}
/**
* 数据库日志
*/
class DatabaseLogger implements Logger {
@Override
public void writeLog(String s) {
System.out.println("数据库日志" + s);
}
}
/***
* 工厂类,创建对应的对象
*/
class FactoryLogger {
public static Logger createLogger() {
// TODO 根据具体业务 您可以选择对应的日志类 只要它是一个ILogger
return new SystemLogger();
}
}
class SomeClass {
public static void main(String[] args) {
// 如果工厂类的实现改变,客户端的代码也不需要有任何的变化
Logger logger = FactoryLogger.createLogger();
logger.writeLog("这是一个日志");
}
}
如果查看这段代码,您可以轻松地将日志记录器实现从 SystemLogger更改为 DatabaseLogger。您只需要修改 createLogger ()函数(这是一个工厂)。对于客户机(业务)代码来说,这种更改是不可见的,因为客户机代码使用了 Logger的接口,而记录器实现的选择是由工厂决定的。通过这样做,您将在日志记录器的实现和使用日志记录器的代码部分之间创建松散耦合。
封装
有时,使用工厂可以提高代码的可读性,并通过封装降低其复杂性。
假设您需要使用一个比较两辆汽车特性的类 CarComparator。这个类需要一个 DatabaseConnection 来获取数百万辆汽车的特性,并需要一个 FileSystemConnection 来获取参数化比较算法的配置文件(例如: 增加的燃料消耗量大于最大速度)。如果没有工厂,你可以编写这样的代码:
public class DatabaseConnection {
DatabaseConnection(String parameters) {
System.out.println("从数据库获取数百万辆汽车的特性");
// TODO
}
}
class FileSystemConnection {
FileSystemConnection(String parameters) {
System.out.println("读取配置文件的代码");
// TODO
}
}
/**
* 比较两辆汽车的特性
*/
class CarComparator {
CarComparator(DatabaseConnection dbConn, FileSystemConnection fsConn) {
// TODO
}
public int compare(String car1, String car2) {
// TODO 业务逻辑代码
return 1;
}
}
class CarBusinessXY {
public static void main(String[] args) {
DatabaseConnection db = new DatabaseConnection("数据库连接");
FileSystemConnection fs = new FileSystemConnection("配置文件地址");
CarComparator carComparator = new CarComparator(db, fs);
carComparator.compare("福特野马5.0T","道奇挑战者");
}
}
class CarBusinessZY {
public static void main(String[] args) {
DatabaseConnection db = new DatabaseConnection("数据库连接");
FileSystemConnection fs = new FileSystemConnection("配置文件地址");
CarComparator carComparator = new CarComparator(db, fs);
carComparator.compare("保时捷918", "法拉利拉法");
}
}
这段代码可以正常工作,但是您可以看到,为了使用比较方法,您需要稍加留意
- 数据库连接
- 一个文件系统连接
- 然后是 CarComparator
如果您需要在多个函数中使用比较,那么您必须复制您的代码,这意味着如果 CarComparator 的结构发生变化,您必须修改所有重复的部分。使用 factory 可以对代码进行因数分解,并隐藏 CarComparator 类构造的复杂性。
// 使用工厂重构
public class Factory {
public static CarComparator getCarComparator() {
DatabaseConnection db = new DatabaseConnection("数据库连接");
FileSystemConnection fs = new FileSystemConnection("配置文件地址");
CarComparator carComparator = new CarComparator(db, fs);
}
}
// 使用工厂的示例代码
class CarBusinessXxx {
public static void main(String[] args) {
CarComparator carComparator = Factory.getCarComparator();
carComparator.compare("福特野马5.0T","道奇挑战者");
}
}
// 使用工厂的示例代码
class CarBusinessYyy {
public static void main(String[] args) {
CarComparator carComparator = Factory.getCarComparator();
carComparator.compare("保时捷918", "法拉利拉法");
}
}
如果你比较这两个代码,你可以看到使用一个工厂:
- 减少代码行数
- 避免代码重复
- 组织代码: 使用工厂构建一个CarComparator,只是使用它的公共方法。
最后一点很重要 (事实上,它们都很重要 ) ,因为这是关于关注点分离的。业务类不应该知道如何构建它需要使用的复杂对象, 业务类只需要关注业务。此外,它还增加了同一个项目的开发人员之间的分工:
- 一个在 CarComparator 和它的创建方式上工作
- 其他人则处理使用 CarComparator 的业务对象
消除歧义
假设您有一个具有多个构造函数(具有非常不同的行为)的类。如何确保不会错误地使用错误的构造函数?让我们来看看下面的代码:
class Example{
// 构造函数 1
public Example(double a, float b) {
// ···
}
// 构造函数 2
public Example(double a) {
// ···
}
// 构造函数 3
public Example(float a, double b) {
// ···
}
}
虽然第一个和第二个构造函数的参数数量不一样,但是你可能很快就无法选择正确的参数,特别是在你忙碌了一天之后,使用你最喜欢的 IDE的自动补全方法。更难看出构造函数1和构造函数3之间的区别。这个示例看起来像一个假的,但是我在项目的遗留代码中看到了它(真实故事!).问题是,如何使用相同类型的参数实现不同的构造函数(同时避免使用类似于构造函数1和3的肮脏方式) ?
下面是一个使用工厂的解决方案:
public class Complex {
public static Complex fromCartesian(double real, double imag) {
return new Complex(real, imag);
}
public static Complex fromPolar(double rho, double theta) {
return new Complex(rho * Math.cos(theta), rho * Math.sin(theta));
}
// 更多的方法
private Complex(double a, double b) {
// ···
}
}
在这个例子中,使用工厂添加了关于创建内容的描述,使用工厂方法名称: 您可以从笛卡尔坐标系或极坐标系创建一个复数。在这两种情况下,你确切地知道创造是关于什么的。
工厂模式
现在我们看到了工厂的利弊,让我们关注不同类型的工厂模式。
我将介绍从最简单到最抽象的每一个工厂。如果你想使用工厂,记住越简单越好。
静态工厂方法
注意: 如果您阅读了本文并且对 Java 了解不多,那么静态方法就是类方法。
Joshua Bloch 在《Effective Java 》一书中描述了静态工厂方法:
一个类可以提供一个公共静态工厂方法,这是一个简单的静态方法,它返回一个类的实例。
换句话说,类可以提供返回实例的静态方法,而不是使用构造函数来创建实例。如果此类具有子类型,则静态工厂方法可以返回该类或其子类型的类型。虽然我讨厌 UML,但是我在文章的开头说过,我将使用 UML 给出一个正式的描述。下面就是:
在这个关系图中,ObjectWithStaticFactory 类有一个静态工厂方法(称为 getObject ())。这个方法可以识别任何类型的 ObjectWithStaticFactory,这意味着类型 ObjectWithStaticFactory 或类型 SubType1或类型 SubType2。当然,这个类可以有其他方法、属性和静态工厂方法。
让我们来看看这段代码:
public class MyClass {
Integer a;
Integer b;
MyClass(int a, int b){
this.a=a;
this.b=b;
};
public static MyClass getInstance(int a, int b){
return new MyClass(a, b);
}
public static void main(String[] args){
// 使用构造函数实例化
MyClass a = new MyClass(1, 2);
// 使用静态工厂方法实例化
MyClass b = MyClass.getInstance(1, 2);
}
}
这段代码展示了创建MyClass实例的两种方法:
- MyClass 中的静态工厂方法 getInstance ()
- MyClass 的构造函数
但是这个概念可以更深入。如果一个具有静态工厂方法的类可以实时存储另一个类,那该怎么办?约书亚 · 布洛赫描述了这种可能性:
接口不能有静态方法,因此根据约定,名为 Type 的接口的静态工厂方法放在名为 Types 的不可实例化类
在这种情况下,factory 方法 getObject 位于抽象类名 Types 中。Factory 方法可以创建类 Type 的实例或类 Type 的任何子类型(图中的 SubType1或 SubType2)。getObject ()方法可以有参数,以便为给定的参数返回 SubType1,否则返回 SubType2。
让我们回到 Java,假设我们有两个类: Ferrari 和 Mustang,它们实现了一个接口 Car。静态工厂方法可以放在一个名为“ CarFactory”的抽象类中(遵循Joshua Boch 的约定,该类的名称应该是“ Cars”,但我不喜欢它) :
public interface Car {
/**
* 造车
*/
void build();
}
class Mustang implements Car {
@Override
public void build() {
System.out.println("这是一辆野马");
}
}
class Ferrari implements Car {
@Override
public void build() {
System.out.println("这是一辆法拉利");
}
}
/**
* 抽象类,不可实例化
*/
abstract class CarFactory {
// 工厂方法,造出不同的车来
public static Car getCar() {
// TODO 选择你想要的车(你的逻辑···)
return new Ferrari();
}
}
/**
* 使用工厂示例
*/
class Employ {
public static void main(String[] args) {
Car myCar = CarFactory.getCar();
myCar.build();
}
}
与其他工厂模式相比,这种模式的强大之处在于您不需要
- 实例化工厂以便使用它(几分钟后您就会明白我的意思)
- 工厂实现一个接口。
它易于使用,但仅适用于提供类方法 ( 即 static java 关键字修饰)。
注意:关于工厂,网络上的许多帖子都是错误的 ,例如stackoverflow上的该帖子被提高了1.5k次。给定的工厂方法模式示例的问题在于它们是静态工厂方法。如果我引用 约书亚·布洛赫(Joshua Bloch)的话:
静态工厂方法不同于设计模式中的工厂方法模式。本文中描述的静态工厂方法在设计模式中没有直接等价物
如果您查看 stackoverflow 文章,那么只有最后一个示例URLStreamHandlerFactory
是 GoF 的工厂方法模式(我们将在几分钟后看到这个模式)
真实的例子
下面是 Java 框架和 Java API 中静态工厂方法的一些示例。在 Java API 中找到示例非常容易,因为 Joshua Bloch 是许多 Java API 的主要架构师。
日志框架(Logging frameworks)
Java 日志框架 slf4j、 logback 和 log4j 使用一个抽象类 LoggerFactory
。如果开发人员想写日志,他需要从 LoggerFactory
的静态方法 getLogger ()
中获得 Logger 的实例。getLogger ()
返回的 Logger 实现将取决于 getLogger ()
的实现以及getLogger ()
使用的开发人员所写的配置文件。
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class Example{
public void example() {
// 使用静态工厂方法来获得Logger的实例
Logger logger = LoggerFactory.getLogger(Example.class);
logger.info("这是一个例子");
}
}
注意: 无论使用 slf4j、 log4j 还是 slf4j,工厂类的名称及其静态工厂方法都不完全相同。
Java String class
Java 中的 String 类表示一个字符串。有时,您需要从布尔值或整数获取字符串。但是 String 不提供像 String (Integer i)或 String (Boolean b)这样的构造函数。相反,它提供了多个静态工厂方法 String.valueOf (…)
int` `i = ``12``;
String integerAsString = String.valueOf(i);
简易工厂
这种模式不是“真实的”,但是我在网上看到过很多次。它没有一个正式的描述,但这里是我的: 一个简单的工厂(或工厂)是一个工具
- 他们的工作是创建/实时化对象
- 既不是工厂方法模式
我们将在后面看到这个模式
- 也不是一个抽象的工厂模式
我们将在后面看到这个模式
您可以看到它具有静态工厂模式的一般化,但是这一次可以暂存(或不暂存)工厂,因为“工厂方法”不是一个类方法(但是它可以)。对于 Java 开发人员来说,使用非静态形式的简单工厂是很少见的。因此,这种模式在大多数情况下等价于静态模式。下面是非静态形式的 UML:
在这种情况下,Factory 方法 getObject ()位于名为 Factory 的类中。Factory 方法不是 class 方法,因此在使用它之前需要先了解 Factory。工厂方法可以创建类 Type 或其任何子类型的实例。
下面是来自静态工厂方法的前一个示例,但是这一次我在使用它之前先对工厂进行了检查
public interface Car {
/**
* 造车
*/
void build();
}
class Mustang implements Car {
@Override
public void build() {
System.out.println("这是一辆野马");
}
}
class Ferrari implements Car {
@Override
public void build() {
System.out.println("这是一辆法拉利");
}
}
/**
* 工厂方法,造出不同的车来
*/
class CarFactory {
/**
* 这个类是可实例化的
*/
public CarFactory() {
// TODO
}
public static Car getCar() {
// TODO 选择你想要的车(你的逻辑···)
return new Ferrari();
}
}
/**
* 使用工厂示例
*/
class Employ {
public static void main(String[] args) {
CarFactory carFactory = new CarFactory();
Car myCar = carFactory.getCar();
myCar.build();
}
}
如你所见,这次我需要留意工厂以便使用它。我没有在 Java 中找到真正的例子,因为使用静态工厂方法比使用简单的工厂更好。不过,如果您的工厂方法需要一些实例才能工作,那么您可以在其非静态形式中使用此模式。例如,如果您需要一个数据库连接,那么您可以首先实现您的工厂(这将实现数据库连接) ,然后使用需要这个连接的工厂方法。就个人而言,在这种情况下,我仍然会使用带有惰性初始模式的静态工厂(以及一个数据库连接池)。
告诉我您是否知道一个 Java 框架,它以非静态的形式使用简单的工厂。
工厂方法模式
工厂方法模式是一个更抽象的工厂。以下是“四人帮”给出的定义:
定义用于创建对象的接口,但让子类决定实例化哪个类。工厂方法允许类将实例化推迟到子类
下面是工厂方法模式的简化 UML 图:
这个图看起来像简单的工厂图以非静态形式
。唯一的也是最大的
不同之处在于界面工厂:
- 工厂代表“用于创建对象的接口”。它描述了一种工厂方法:getObjects()
- ConcreteFactory 代表“决定实例化哪个类的子类之一”。每个 ConcreteFactory 都有自己的工厂方法 getObjects ()的实现
在关系图中,getObjects ()必须返回一个 Type (或其子类型)。这意味着一个 contrete factory 可以返回 Subtype1,而另一个可以返回 SubType2。
为什么使用工厂方法模式而不是简单的工厂?
仅当您的代码需要多个工厂实现时。这将迫使每个工厂实现具有相同的逻辑,以便使用一个实现的开发人员可以轻松地切换到另一个实现,而不必担心如何使用它(因为他只需调用具有相同签名的工厂方法)。
由于这是抽象的,让我们回到汽车的例子。这不是一个很好的例子,但是我使用它是为了让你看到简单工厂的不同之处(我们将看到真实的示例以了解此模式的强大功能) :
public interface Car {
/**
* 造车
*/
void build();
}
class Mustang implements Car {
@Override
public void build() {
System.out.println("这是一辆野马");
}
}
class Ferrari implements Car {
@Override
public void build() {
System.out.println("这是一辆法拉利");
}
}
/**
* 工厂方法,造出不同的车来
*/
interface CarFactory {
Car getCar();
}
/**
* 具有getCar()工厂方法实现的真实工厂
*/
class ConcreteCarFactory implements CarFactory{
// 这个类是可实例化的
public ConcreteCarFactory(){
// TODO
}
@Override
public Car getCar() {
// TODO 选择你想要的车(你的逻辑···)
return new Ferrari();
}
}
/**
* 使用工厂示例
*/
class Employ {
public static void main(String[] args) {
CarFactory carFactory = new ConcreteCarFactory();
Car myCar = carFactory.getCar();
myCar.build();
}
}
如果将此代码与简单工厂进行比较,这次我添加了一个接口(CarFactory)。真正的工厂(ConcreteCarFactory)实现了这个接口。
正如我说的那样,这不是一个很好的示例,因为在此示例中,您不应该使用工厂方法模式,因为 只有一个具体的工厂。 这将是唯一的,如果我有多个实现像有用SportCarFactory,VintageCarFactory,LuxeCarFactory,CheapCarFactory …。在这种情况下,由于工厂方法始终是getCar(),因此开发人员可以轻松地从一种实现切换到另一种实现。
真实的例子
在 Java中,一个常见的例子是集合 API 中的 Iterable()函数。每个集合都实现了接口 Iterable < e > 。这个接口描述了一个函数迭代器() ,它返回一个迭代器 < e > 。数组列表 < e > 是一个集合。因此,它实现了接口 Iterable < e > 和它的工厂方法 Iterator () ,后者返回 Iterator < e > 的子类
// 下面是来自java源代码的迭代器的简化定义
public interface Iterator<E> {
// 如果迭代具有更多元素,则返回 true
boolean hasNext();
// 返回迭代中的下一个元素
E next();
// 从底层集合中删除此迭代器返回的最后一个元素
void remove();
}
// 工厂接口
public interface Iterable<T> {
Iterator<T> iterator();
}
// 下面是java源代码中对ArrayList的简化定义
// 你可以看到这个类是一个实现的具体工厂
// 工厂方法迭代器()
// 注意:在实际的Java源代码中,ArrayList是由
// AbstractList,它使用工厂实现了工厂方法模式
public class ArrayList<E> {
// iterator()返回一个子类型和一个“匿名”迭代器<E>
public Iterator<E> iterator()
{
return new Iterator<E>()
{
// 实现方法hasNext(), next()和remove()
};
}
}
下面是 ArrayList 的一个标准用法
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
public class Example {
public static void main(String[] ars){
// 实例化(具体工厂)
List<Integer> myArrayList = new ArrayList<>();
// 调用ArrayList的工厂方法iterator()
Iterator<Integer> myIterator = myArrayList.iterator();
}
}
我展示了一个ArrayList,但是我可以展示HashSet,LinkedList或HashMap,因为它们都是集合API的一部分。这种模式的优势在于您不需要知道您使用的是哪种类型的集合,每个集合都将通过工厂方法iterator()提供一个Iterator。
另一个很好的例子是 Java 8 CollectionAPI中的stream()
方法。
SPring
Spring 框架基于工厂方法模式。ApplicationContext 实现 BeanFactory 接口。此接口描述返回 Object 的函数 Object getBean (param)。这个例子很有趣,因为 java 中的每个 Class 都是从 Object 派生的。因此,这个工厂可以返回任何类的实例取决于参数
。
public class Example{
public static void main(String[] args) {
// 创建BeanFactory
ApplicationContext context = new ClassPathXmlApplicationContext("config.xml");
// 用工厂创建完全不同类型的对象
MyType1 objectType1 = context.getBean("myType1");
MyType2 objectType2 = context.getBean("myType2");
}
}
The abstract factory 抽象工厂
四人帮这样描述这家工厂:提供一个接口,用于创建相关或依赖对象的系列,而不指定它们的具体类
如果你不理解这个句子,不要担心,这是正常的。它不是无缘无故被称为抽象工厂!
如果它可以帮助您,我将抽象工厂模式看作是工厂方法模式的泛化,这次工厂接口有多个相关的工厂方法。当我说相关的时候,我的意思是概念上的联系,这样它们就形成了一个工厂方法的“家庭”。让我们看看 UML 图,看看它与工厂方法模式的区别:
- Factory是定义多个工厂方法的接口,在我们的示例中为:getObject1()和getObject2()。每个方法都会创建一个类型(或其子类型)。
- ConcreteFactory实现Factory接口,因此具有自己的getObject1()和getObject2()实现。现在假设有两个具体工厂:一个可以返回
SubType1.1
和SubType2.1
的实例,另一个可以返回SubType1.2
和SubType2.2
的实例。
由于这是非常抽象的,让我们回到 CarFactory 示例。
使用工厂方法模式,工厂接口只有一种方法getCar()
。抽象工厂可以是具有3种工厂方法的接口:getEngine()
,getBody()
和getWheel()
。您可能有多个具体工厂:
- SportCarFactory 可以返回PowerfulEngine,RaceCarBody和RaceCarWheel的实例
- CheapCarFactory 可以返回WeakEngine,HorribleBody和RottenWheel的实例
如果要制造跑车,则需要实例化SportCarFactory然后使用它。而且,如果您想制造一辆廉价汽车,则需要实例化CheapCarFactory然后使用它。
这个抽象工厂的3种工厂方法是相关的。它们都属于汽车生产概念。
当然,工厂方法可以具有参数,以便它们返回不同的类型。例如,SportCarFactory的 getEngine(String model)工厂可以根据参数返回Ferrari458Engine或FerrariF50Engine或Ferrari450Engine或···。
下面是 Java中的示例只有 SportCarFactory 和2个 factory 方法
。
/**
* 车轮、方向盘
*/
public interface Wheel {
void turn();
}
class RaceCarWheel implements Wheel {
@Override
public void turn() {
System.out.println("制造车轮,方向盘,车身等");
}
}
/**
* 发动机
*/
interface Engine {
void work();
}
class PowerfulEngine implements Engine {
@Override
public void work() {
System.out.println("制造一个发动机");
}
}
/**
* 工厂
*/
interface CarFactory {
// 返回车子的发动机
Engine getEngine();
// 返回车子其他部分
Wheel getWheel();
}
class SportCarFactory implements CarFactory {
@Override
public Engine getEngine() {
// 制造一个发动机
return new PowerfulEngine();
}
@Override
public Wheel getWheel() {
// 制造车子其他部分
return new RaceCarWheel();
}
}
class SomeClass {
public static void main(String[] args) {
CarFactory carFactory = new SportCarFactory();
Wheel myWheel= carFactory.getWheel();
myWheel.turn();
Engine myEngine = carFactory.getEngine();
myEngine.work();
}
}
这个工厂不容易,你什么时候用呢?
~~决不!!!!~~哼,很难回答。我将这种工厂模式视为组织代码的一种方式。如果您在代码中得到许多工厂方法模式,并且看到了它们之间的共同主题,则可以使用抽象工厂来收集该组。我不赞成“让我们使用抽象工厂,因为将来我们可能需要一个抽象工厂”,因为这种模式非常抽象。我更喜欢构建简单的东西,并在需要后对其进行重构。
但是,常见的用例是需要创建外观和感觉不同的用户界面。这个例子被“四人帮”用来呈现这种模式。该UI需要一些产品,例如窗口,滚动条,按钮…您可以为每种外观创建带有具体工厂的Factory。当然,此示例是在Internet时代之前编写的,现在您甚至可以为桌面应用程序使用CSS(或某些脚本语言)来拥有一个组件并修改其外观。这意味着大多数情况下,静态工厂方法就足够了。
但是,如果您仍然想使用此模式,则可以使用GoFGang of Four的简称 -> 设计模式:可复用面向对象软件的基础 的作者
中的一些用例:
“系统应配置有多个产品系列之一”
“您想提供产品的类库,并且只想显示它们的接口,而不是它们的实现”
真实的例子
大多数DAO(数据访问对象)框架使用抽象工厂来指定具体工厂应该执行的基本操作。尽管工厂方法的名称取决于框架,但通常不适合:
- createObject(…)或persistObject(…)
- updateObject(…)或saveObject(…)
- deleteObject(…)或removeObject(…)
- readObject(…)或findObject(…)
对于您处理的每种类型的对象,您都需要一个具体的工厂。例如,如果您需要使用数据库来管理人员,房屋和合同。我将有一个PersonFactory,一个HouseFactory和一个ContractFactory。
Spring的CrudRepository是抽象工厂的一个很好的例子。
如果您需要Java代码,则可以查找JPA,Hibernate或SpringData教程。
结论
我希望您现在对不同类型的工厂模式以及何时使用它们有所了解。尽管我在本文中多次说过,但请记住,大多数情况下,工厂会使代码更复杂/更抽象。即使您了解工厂(如果您不知道,请再次阅读本文!),您的同事又如何呢?但是,在中型/大型应用程序上工作时,值得使用工厂。
我在工厂苦苦挣扎了很长时间,以了解是哪一个,像这样的一篇文章对我有帮助。我希望本文是可以理解的,并且我没有写太多错误。随时告诉我您阅读的内容是否困扰您,以便我改善本文。