设计模式基础——工厂模式剖析(2/2)
目录
一、工厂模式
1.1 工厂模式的定义
1.2 工厂模式的设计意图
1.3 工厂模式主要解决的问题
1.4 工厂模式的缺点
1.5 实际的应用案例
1. 数据库连接池
2. 图形用户界面(GUI)组件
3. 文件操作
二、各种工厂模式的变形
1.1 简单工厂模式:
1.2 工厂方法模式:
1.3 抽象工厂模式
三、基本的代码实现
今天重点梳理下设计模式中,工厂模式的相关知识点。
一、工厂模式
1.1 工厂模式的定义
工厂模式(Factory Pattern)最常用的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。
在工厂模式中,我们在创建对象时不会对客户端暴露创建逻辑,并且是通过使用一个共同的接口来指向新创建的对象。
1.2 工厂模式的设计意图
工厂模式的主要设计意图是将对象的创建过程与使用过程分离,从而使得客户端代码无需知道具体创建什么类型的对象。这种分离有助于降低系统的耦合度,提高代码的可维护性和扩展性。
工厂模式通过以下方式实现这一目标:
1. 解耦对象的创建
- 工厂模式允许将对象的创建过程封装在一个单独的类或一组类中(即工厂),而不是直接在客户端代码中实例化对象。
- 这样可以避免在客户端代码中硬编码具体的对象类型,使得代码更加灵活和可复用。
2. 支持多种产品类型
- 工厂模式可以根据不同的情况动态地选择要创建的对象类型,而客户端只需关心如何使用产品,而不必关心产品的具体实现细节。
- 这种灵活性使得工厂模式适用于需要根据运行时条件来决定创建哪种产品的情况。
3. 隐藏复杂的产品创建逻辑
- 如果对象的创建过程非常复杂或者涉及到多个步骤,工厂模式可以通过将这些逻辑封装在工厂类中来简化客户端代码。
- 客户端代码只需要调用工厂类的一个简单方法就可以得到所需的对象,而不需要了解具体的创建过程。
4. 易于添加新产品类型
- 当需要添加新的产品类型时,只需增加一个新的工厂类即可,而无需修改现有的客户端代码。
- 这种开闭原则(Open-Closed Principle)的遵循使得系统更容易进行扩展。
5. 便于单元测试
- 由于工厂模式将对象的创建过程隔离出来,因此在编写单元测试时可以方便地替换实际的产品对象为模拟对象(mock object),以减少依赖并提高测试覆盖率。
1.3 工厂模式主要解决的问题
- 对象的创建过于复杂:如果一个对象的创建涉及到很多步骤,或者需要复杂的条件判断,那么工厂模式可以帮助将这些逻辑封装在一个地方,使得客户端代码更简洁。
- 过度依赖具体的产品类:如果没有使用工厂模式,客户端代码可能会直接引用具体的产品类,这会导致高度的耦合。工厂模式可以将这种依赖关系转移到工厂类上,从而降低耦合度。
- 难以扩展新类型的产品:如果系统中有很多不同种类的产品,而且可能还会继续增加,那么工厂模式可以提供一种统一的方式来管理这些产品,使得添加新类型的产品变得相对容易。
- 需要控制对象的生命周期:在某些情况下,可能需要对对象的创建、使用和销毁进行更精细的控制。工厂模式可以通过返回不同类型的产品实例来实现这一点。
1.4 工厂模式的缺点
-
增加了代码复杂性:引入工厂模式后,系统中会多出一个或多个工厂类以及可能的产品类,这可能会增加代码的复杂性,尤其是在大型项目中。
-
违反开闭原则:尽管工厂模式有助于扩展新的产品类型,但如果需要改变已有的工厂类来支持新类型,就违背了开闭原则。这意味着每次添加新产品类型时都可能需要修改工厂类的代码。
-
可能增加额外的性能开销:工厂模式通常涉及一些条件判断或反射操作来决定创建哪种产品对象,这可能会带来一些额外的性能开销。
-
难以理解复杂的工厂结构:在某些情况下,工厂模式可能导致工厂类的结构变得复杂,特别是当存在大量产品类型和工厂类时。这可能会使代码更难理解和维护。
-
不适合简单的场景:如果只需要创建一种产品类型,或者创建过程非常简单,那么使用工厂模式可能会显得过于复杂,此时直接使用new关键字创建对象可能是更好的选择。
1.5 实际的应用案例
1. 数据库连接池
在数据库应用程序中,通常需要频繁地创建和销毁数据库连接。为了提高性能和减少资源消耗,可以使用连接池来管理这些连接。连接池就是一个典型的工厂模式应用。
首先定义一个DatabaseConnection
接口,表示数据库连接的抽象类型。然后创建多个实现这个接口的具体类,例如MySQLConnection
、PostgreSQLConnection
等,它们分别代表与不同类型的数据库建立连接。
接下来创建一个DatabaseConnectionFactory
接口,声明一个方法createConnection()
用于创建数据库连接。针对每种数据库类型,都创建一个具体的工厂类,如MySQLConnectionFactory
、PostgreSQLConnectionFactory
等,它们实现了DatabaseConnectionFactory
接口,并且在createConnection()
方法中返回对应的数据库连接实例。
客户端代码只需要知道如何使用DatabaseConnection
接口,而不必关心具体使用的数据库类型。当需要创建新的数据库连接时,只需调用相应的工厂类的createConnection()
方法即可。
2. 图形用户界面(GUI)组件
在图形用户界面编程中,工厂模式也可以用来创建各种UI组件。例如,我们可以定义一个Button
接口,表示按钮的抽象类型。然后为每个平台(如Windows、MacOS、Linux等)创建一个实现Button
接口的具体类,比如WindowsButton
、MacOSButton
、LinuxButton
等。
接着定义一个ButtonFactory
接口,声明一个方法createButton()
用于创建按钮。为每个平台创建一个具体的工厂类,如WindowsButtonFactory
、MacOSButtonFactory
、LinuxButtonFactory
等,它们实现了ButtonFactory
接口,并且在createButton()
方法中返回对应的按钮实例。
客户端代码可以根据运行时环境选择合适的工厂类来创建按钮。这样可以使代码更加灵活,更容易适应不同平台的需求。
3. 文件操作
在处理文件时,可以使用工厂模式来根据文件的扩展名创建适当的文件处理器。首先定义一个FileProcessor
接口,包含读取、写入等操作。然后为每种文件格式创建一个实现FileProcessor
接口的具体类,如TextFileProcessor
、ImageFileProcessor
、AudioFileProcessor
等。
接下来定义一个FileProcessorFactory
接口,声明一个方法createProcessor(filename)
用于根据文件名创建文件处理器。为每种文件格式创建一个具体的工厂类,如TextFileProcessorFactory
、ImageFileProcessorFactory
、AudioFileProcessorFactory
等,它们实现了FileProcessorFactory
接口,并且在createProcessor()
方法中根据文件扩展名返回对应的文件处理器实例。
客户端代码只需要知道如何使用FileProcessor
接口,而不必关心处理哪种类型的文件。当需要对文件进行操作时,只需调用相应的工厂类的createProcessor()
方法即可。
以上是三个具体的工厂模式应用案例。实际上,任何需要动态创建对象并且有多种可能的产品类型的情况,都可以考虑使用工厂模式。您需要一辆汽车,可以直接从工厂里面提货,而不用去管这辆汽车是怎么做出来的,以及这个汽车里面的具体实现。Hibernate 换数据库只需换方言和驱动就可以。
二、各种工厂模式的变形
工厂模式主要有以下几种形式:
1.1 简单工厂模式:
- 定义一个工厂类,该类可以根据传入的参数返回不同种类的产品实例。
- 简单工厂模式不是Gang of Four (GoF)所定义的23种经典设计模式之一,但它是一个常用的设计模式变体,也被广泛称为静态工厂方法。
1.2 工厂方法模式:
- 在抽象基类中声明一个创建产品的方法,但不提供具体的实现,由子类来实现这个方法。
- 每个子类根据需要创建自己的产品类型。
- 这种模式支持添加新的产品类型时只需要增加一个新的子类即可。
1.3 抽象工厂模式
- 提供一个创建一系列相关或相互依赖对象的接口,而无需指定具体的类。
- 适用于需要提供多个相关的对象系列的情况。
- 与工厂方法模式相比,抽象工厂模式更注重的是创建一组相关或相互依赖的产品。
三、基本的代码实现
下面是一个简单的工厂模式的示例,使用 Java 语言来实现:
// 定义一个接口或抽象类
public interface Product {
void use();
}
// 实现具体的产品1
public class ConcreteProduct1 implements Product {
@Override
public void use() {
System.out.println("Using concrete product 1.");
}
}
// 实现具体的产品2
public class ConcreteProduct2 implements Product {
@Override
public void use() {
System.out.println("Using concrete product 2.");
}
}
// 工厂类
public class Factory {
// 根据传入的参数返回不同的产品
public static Product createProduct(String type) {
if ("product1".equals(type)) {
return new ConcreteProduct1();
} else if ("product2".equals(type)) {
return new ConcreteProduct2();
} else {
throw new IllegalArgumentException("Invalid type");
}
}
}
// 客户端代码
public class Client {
public static void main(String[] args) {
Product product1 = Factory.createProduct("product1");
product1.use();
Product product2 = Factory.createProduct("product2");
product2.use();
}
}
在上面的示例中,我们定义了一个`Product`接口和两个实现了该接口的具体产品类`ConcreteProduct1`和`ConcreteProduct2`。然后我们创建了一个`Factory`类,它有一个静态方法`createProduct`,根据传入的字符串参数返回不同种类的产品实例。
客户端代码通过调用`Factory.createProduct`方法来获取需要的产品实例,并使用它们。
如果你想要 Python 的示例代码,下面是等效的实现:
# 定义一个抽象基类
from abc import ABC, abstractmethod
class Product(ABC):
@abstractmethod
def use(self):
pass
# 实现具体的产品1
class ConcreteProduct1(Product):
def use(self):
print("Using concrete product 1.")
# 实现具体的产品2
class ConcreteProduct2(Product):
def use(self):
print("Using concrete product 2.")
# 工厂类
class Factory:
# 根据传入的参数返回不同的产品
@staticmethod
def create_product(product_type):
if product_type == "product1":
return ConcreteProduct1()
elif product_type == "product2":
return ConcreteProduct2()
else:
raise ValueError("Invalid type")
# 客户端代码
def main():
product1 = Factory.create_product("product1")
product1.use()
product2 = Factory.create_product("product2")
product2.use()
if __name__ == "__main__":
main()
这段 Python 代码的功能与上述 Java 代码相同,只是语法有所不同。在这里,我们使用了 Python 的 `abc` 模块来定义一个抽象基类(抽象类),并利用 `@abstractmethod` 装饰器来标记需要子类实现的方法。其他部分的逻辑和结构基本保持一致。
PS:设计模式的梳理系列,暂时告一段落。谢谢阅读。