工厂方法模式
引言
设计模式是软件工程中的一种常见实践,它们提供了解决软件设计中常见问题的通用解决方案。其中一个最常用且实用的设计模式是工厂方法模式。
工厂方法模式是一种创建型设计模式,它提供了一种封装对象创建过程的方式。在工厂方法模式中,我们不直接使用 new
关键字来创建对象,而是通过一个工厂方法来创建对象。这样,我们可以在不改变代码的情况下更改所创建对象的类型。
工厂方法模式结构
举例说明
图形界面组件
假设我们正在开发一个跨平台的客户端软件,需要支持多种操作系统(如 Windows、MacOS、Linux 等)。每种操作系统都有自己独特的方式来创建和管理软件的窗口和控件,但我们希望能提供一个统一的接口来创建所有类型的组件。
首先,我们定义一个 Component
接口,它定义了所有组件必须实现的方法:
module Component
def draw
raise NotImplementedError, 'You must implement the draw method'
end
end
然后,我们为每种支持的操作系统创建一个具体类。这些类实现了 Component
接口:
class WindowsButton
include Component
def draw
# 使用 Windows API 来绘制按钮...
end
end
class MacOSButton
include Component
def draw
# 使用 MacOS API 来绘制按钮...
end
end
class LinuxButton
include Component
def draw
# 使用 Linux API 来绘制按钮...
end
end
接下来,我们创建一个 `ComponentFactory` 类,它提供了一个工厂方法来创建不同类型的组件:
class ComponentFactory
def self.create_component(type)
case type
when :windows
WindowsButton.new
when :macos
MacOSButton.new
when :linux
LinuxButton.new
else
raise "Invalid component type: #{type}"
end
end
end
现在,无论我们需要哪种类型的组件,都可以使用相同的代码来创建:
button = ComponentFactory.create_component(:windows)
button.draw
### 数据库连接
假设我们正在开发一个应用,需要支持多种数据库(如MySQL、PostgreSQL、SQLite等)。每种数据库都有自己独特的连接方式和参数,但我们希望能提供一个统一的接口来创建所有类型的数据库连接。
首先,我们定义一个 `DatabaseConnection` 接口,它定义了所有数据库连接必须实现的方法:
module DatabaseConnection
def connect(params)
raise NotImplementedError, 'You must implement the connect method'
end
def disconnect
raise NotImplementedError, 'You must implement the disconnect method'
end
end
然后,我们为每种支持的数据库创建一个具体类。这些类实现了 `DatabaseConnection` 接口:
class MySQLConnection
include DatabaseConnection
def connect(params)
# 使用 MySQL API 来建立连接...
end
def disconnect
# 断开 MySQL 连接...
end
end
class PostgreSQLConnection
include DatabaseConnection
def connect(params)
# 使用 PostgreSQL API 来建立连接...
end
def disconnect
# 断开 PostgreSQL 连接...
end
end
class SQLiteConnection
include DatabaseConnection
def connect(params)
# 使用 SQLite API 来建立连接...
end
def disconnect
# 断开 SQLite 连接...
end
end
接下来,我们创建一个 `DatabaseConnectionFactory` 类,它提供了一个工厂方法来创建不同类型的数据库连接:
class DatabaseConnectionFactory
def self.create_connection(type)
case type
when :mysql
MySQLConnection.new
when :postgresql
PostgreSQLConnection.new
when :sqlite
SQLiteConnection.new
else
raise "Invalid database type: #{type}"
end
end
end
现在,无论我们需要哪种类型的数据库连接,都可以使用相同的代码来创建并使用:
connection = DatabaseConnectionFactory.create_connection(:mysql)
connection.connect({host: 'localhost', user: 'root', password: 'password'})
Do some database operations…
connection.disconnect()
### 支付系统
假设我们正在开发一个电商应用,需要支持多种支付方式(如信用卡支付、PayPal支付、比特币支付等)。每种支付方式都有自己独特的实现方式,但我们希望能提供一个统一的接口来创建所有类型的支付对象。
首先,我们定义一个 Payment 接口,它定义了所有支付对象必须实现的方法:
module Payment
def pay(amount)
raise NotImplementedError, 'You must implement the pay method'
end
end
然后,我们为每种支持的支付方式创建一个具体类。这些类实现了 `Payment` 接口:
class CreditCardPayment
include Payment
def pay(amount)
# 使用信用卡支付 API 来处理支付...
puts "Paid #{amount} using Credit Card"
end
end
class AliPayPayment
include Payment
def pay(amount)
# 使用 AliPay API 来处理支付...
puts "Paid #{amount} using AliPay"
end
end
class BitcoinPayment
include Payment
def pay(amount)
# 使用比特币支付 API 来处理支付...
puts "Paid #{amount} using Bitcoin"
end
end
接下来,我们创建一个 `PaymentFactory` 类,它提供了一个工厂方法来创建不同类型的支付对象:
class PaymentFactory
def self.create_payment(type)
case type
when :credit_card
CreditCardPayment.new
when :alipay
AliPayPayment.new
when :bitcoin
BitcoinPayment.new
else
raise "Invalid payment type: #{type}"
end
end
end
现在,无论我们需要哪种类型的支付方式,都可以使用相同的代码来创建并使用:
payment = PaymentFactory.create_payment(:credit_card)
payment.pay(100.00)
## 工厂方法模式适合应用场景
### 当我们在编写代码的过程中, 如果无法预知对象确切类别及其依赖关系时, 可使用工厂方法。
工厂方法将创建产品的代码与实际使用产品的代码分离, 从而能在不影响其他代码的情况下扩展产品创建部分代码。
例如, 如果需要向应用中添加一种新产品, 我们只需要开发新的创建者子类, 然后重写其工厂方法即可。
### 如果我们希望用户能扩展我们软件库或框架的内部组件, 可使用工厂方法。
继承可能是扩展软件库或框架默认行为的最简单方法。 但是当我们使用子类替代标准组件时, 框架如何辨识出该子类?
解决方案是将各框架中构造组件的代码集中到单个工厂方法中, 并在继承该组件之外允许任何人对该方法进行重写。
让我们看看具体是如何实现的。 假设我们使用开源 UI 框架编写自己的应用。 我们希望在应用中使用圆形按钮, 但是原框架仅支持矩形按钮。 我们可以使用 `圆形按钮`RoundButton子类来继承标准的 `按钮`Button类。 但是, 我们需要告诉 `UI框架`UIFramework类使用新的子类按钮代替默认按钮。
为了实现这个功能, 我们可以根据基础框架类开发子类 `圆形按钮 UI`UIWithRoundButtons , 并且重写其 `createButton`创建按钮方法。 基类中的该方法返回 `按钮`对象, 而我们开发的子类返回 `圆形按钮`对象。 现在, 我们就可以使用 `圆形按钮 UI`类代替 `UI框架`类。 就是这么简单!
### 如果我们希望复用现有对象来节省系统资源, 而不是每次都重新创建对象, 可使用工厂方法。
在处理大型资源密集型对象 (比如数据库连接、 文件系统和网络资源) 时, 我们会经常碰到这种资源需求。
让我们思考复用现有对象的方法:
1. 首先, 我们需要创建存储空间来存放所有已经创建的对象。
2. 当他人请求一个对象时, 程序将在对象池中搜索可用对象。
3. … 然后将其返回给客户端代码。
4. 如果没有可用对象, 程序则创建一个新对象 (并将其添加到对象池中)。
这些代码可不少! 而且它们必须位于同一处, 这样才能确保重复代码不会污染程序。
可能最显而易见, 也是最方便的方式, 就是将这些代码放置在我们试图重用的对象类的构造函数中。 但是从定义上来讲, 构造函数始终返回的是**新对象**, 其无法返回现有实例。
因此, 我们需要有一个既能够创建新对象, 又可以重用现有对象的普通方法。 这听上去和工厂方法非常相像。
## 工厂方法模式优缺点
### 优点
- 我们可以避免创建者和具体产品之间的紧密耦合。
- *单一职责原则*。 我们可以将产品创建代码放在程序的单一位置, 从而使得代码更容易维护。
- *开闭原则*。 无需更改现有客户端代码, 我们就可以在程序中引入新的产品类型。
### 缺点
- 应用工厂方法模式需要引入许多新的子类, 代码可能会因此变得更复杂。 最好的情况是将该模式引入创建者类的现有层次结构中。
## 与其他模式的关系
- 在许多设计工作的初期都会使用[工厂方法模式](https://refactoringguru.cn/design-patterns/factory-method) (较为简单, 而且可以更方便地通过子类进行定制), 随后演化为使用[抽象工厂模式](https://refactoringguru.cn/design-patterns/abstract-factory)、 [原型模式](https://refactoringguru.cn/design-patterns/prototype)或[生成器模式](https://refactoringguru.cn/design-patterns/builder) (更灵活但更加复杂)。
- [抽象工厂模式](https://refactoringguru.cn/design-patterns/abstract-factory)通常基于一组[工厂方法](https://refactoringguru.cn/design-patterns/factory-method), 但我们也可以使用[原型模式](https://refactoringguru.cn/design-patterns/prototype)来生成这些类的方法。
- 我们可以同时使用[工厂方法](https://refactoringguru.cn/design-patterns/factory-method)和[迭代器模式](https://refactoringguru.cn/design-patterns/iterator)来让子类集合返回不同类型的迭代器, 并使得迭代器与集合相匹配。
- [原型](https://refactoringguru.cn/design-patterns/prototype)并不基于继承, 因此没有继承的缺点。 另一方面, *原型*需要对被复制对象进行复杂的初始化。 [工厂方法](https://refactoringguru.cn/design-patterns/factory-method)基于继承, 但是它不需要初始化步骤。
- [工厂方法](https://refactoringguru.cn/design-patterns/factory-method)是[模板方法模式](https://refactoringguru.cn/design-patterns/template-method)的一种特殊形式。 同时, *工厂方法*可以作为一个大型*模板方法*中的一个步骤。
## 总结
工厂方法模式是一种实用的设计模式,它封装了对象创建过程,增强了代码的可维护性和扩展性。在图形界面组件、数据库连接和支付系统的应用中,我们看到了其灵活性:可以轻松地切换创建的对象类型,而无需修改代码。这种模式遵循了开闭原则,使得代码对扩展开放,对修改关闭。
评论区