Python Coding之设计模式
我们来分别介绍一下设计模式中的工厂模式、建造者模式、单例模式和策略模式。前三个都属于创建型模式(Creational Patterns),它们主要关注对象的创建过程,旨在将对象的创建与使用解耦,提高系统的灵活性和可维护性,策略模式属于行为型模式 (Behavioral Pattern),它侧重于对象之间的职责分配和算法封装。为了方便理解,每个模式我们都会用一个改造实例来说明。
1. 工厂模式 (Factory Pattern)
工厂模式是一种创建型设计模式,它提供了一种创建对象的最佳方式,隐藏了对象的具体创建逻辑,客户端只需要知道所需产品的接口或抽象类,而无需关心其具体实现。
- 核心思想:定义一个用于创建对象的接口(或抽象类),让子类决定实例化哪一个类。工厂方法使一个类的实例化延迟到其子类。
- 解决的问题:
- 客户端代码与具体产品类的创建紧密耦合。如果需要更换或增加产品,就需要修改客户端代码。
- 对象的创建过程可能比较复杂,包含很多初始化逻辑,不希望这些逻辑散布在客户端各处。
- 主要变体:
- 简单工厂模式 (Simple Factory Pattern):一个工厂类根据传入的参数决定创建哪种产品类的实例。严格来说它不属于 GoF 23 种设计模式,但非常常用。缺点是增加新产品需要修改工厂类,违反开闭原则。
- 工厂方法模式 (Factory Method Pattern):定义一个创建对象的接口,但让实现这个接口的子类来决定实例化哪个类。每个具体产品对应一个具体工厂。符合开闭原则。
- 抽象工厂模式 (Abstract Factory Pattern):提供一个接口,用于创建一系列相关或相互依赖的对象,而无需指定它们具体的类。它创建的是一个“产品族”。
- 优点:
- 解耦:将对象的创建和使用分离,客户端不依赖具体产品类。
- 灵活性:更容易更换、增加新的产品实现。
- 封装性:隐藏了复杂的对象创建逻辑。
- 缺点:
- 每增加一个产品,可能需要增加一个具体产品类和对应的工厂类(工厂方法模式),导致类的个数成倍增加。
- 抽象工厂模式理解和实现相对复杂。
- 适用场景:
- 当你不知道需要创建的具体对象类型时。
- 当你希望将对象的创建与使用分离时。
- 当你需要创建一系列相关的对象时(抽象工厂)。
- 例如:数据库连接
Connection
的创建(根据不同数据库类型返回不同的Connection
实现)、日志记录器、UI 控件的创建等。
1.1原代码:
1 |
|
1.2工厂模式改造:
1 |
|
2. 建造者模式 (Builder Pattern)
建造者模式是一种创建型设计模式,它将一个复杂对象的构建过程与其表示分离,使得同样的构建过程可以创建不同的表示。它允许你分步骤地构建复杂对象。
- 核心思想:使用多个简单的对象一步一步构建成一个复杂的对象。一个
Builder
类通常用于封装构建过程的细节。 - 解决的问题:
- 当一个对象的构造函数参数过多(尤其是很多可选参数)时,构造函数会变得非常冗长和难以使用(Telescoping Constructor 问题)。
- 直接通过 setter 方法设置属性,可能导致对象在构建完成前处于不一致或不完整状态。
- 需要精细控制对象的构建过程,或者构建过程有多个步骤。
- 主要组成部分:
Product
(产品):要构建的复杂对象。Builder
(抽象建造者):定义了构建产品各个部分的接口。ConcreteBuilder
(具体建造者):实现了Builder
接口,负责具体构建和装配产品的各个部分。Director
(指挥者,可选):负责安排构建步骤的顺序,使用Builder
接口来构建产品。客户端也可以直接充当指挥者。
- 优点:
- 封装性好:构建过程和最终表示分离。
- 控制精细:可以分步构建,更好地控制构建过程。
- 代码可读性强:对于多参数对象,链式调用
builder.setXxx().setYyy().build()
比长参数列表的构造函数更清晰。 - 易于扩展:可以方便地增加新的具体建造者来改变产品的内部表示。
- 缺点:
- 需要为每个产品创建对应的
Builder
类,增加了代码量。 - 模式本身相对复杂一些。
- 需要为每个产品创建对应的
- 适用场景:
- 需要创建的对象具有复杂的内部结构(多个组成部分)。
- 对象的属性之间有依赖关系或创建顺序有要求。
- 希望对象的构建过程和最终表示分离。
- 当构造函数参数过多时,或者想创建不可变对象时。
- 例如:
StringBuilder
/StringBuffer
(虽然简单,但体现了分步构建思想)、构建复杂的配置对象、生成复杂的报表文档、组装车辆等。
2.1原代码
1 |
|
2.2建造者模式改造:
1 |
|
3. 单例模式 (Singleton Pattern)
单例模式是一种创建型设计模式,它确保一个类只有一个实例,并提供一个全局访问点来访问该实例。
- 核心思想:限制类的实例化,保证在整个应用程序生命周期中,特定类只有一个对象存在。
- 解决的问题:
- 某些类只需要一个实例来协调系统全局的行为,例如配置管理器、日志记录器、线程池、数据库连接池等。
- 频繁创建和销毁全局使用的对象会造成性能开销。
- 需要一个全局唯一的访问点来获取这个实例。
- 实现要点:
- 私有化构造函数:防止外部通过
new
操作符直接创建实例。 - 静态私有成员变量:持有类的唯一实例。
- 静态公有工厂方法(通常命名为
getInstance()
):负责创建(如果尚未创建)并返回这个唯一实例。
- 私有化构造函数:防止外部通过
- 常见实现方式:
- 饿汉式 (Eager Initialization):类加载时就创建实例,线程安全,但可能造成资源浪费(如果实例一直未使用)。
- 懒汉式 (Lazy Initialization):第一次调用
getInstance()
时才创建实例。需要处理多线程环境下的线程安全问题(例如使用synchronized
或双重检查锁定 Double-Checked Locking)。 - 静态内部类 (Static Inner Class):结合了懒加载和线程安全,推荐使用。
- 枚举 (Enum):最简洁、最安全的实现方式,可以防止反射和反序列化攻击,推荐使用。
- 优点:
- 保证唯一实例:确保了资源的一致性访问(如配置文件)。
- 全局访问点:方便访问。
- 延迟实例化(懒汉式):节省资源。
- 缺点:
- 违反单一职责原则:类既负责业务逻辑,又负责管理自己的实例。
- 隐藏依赖:全局状态使得代码的依赖关系不明确,不易于理解和测试。
- 可测试性差:全局状态很难模拟(mock)和隔离,给单元测试带来困难。
- 对继承不友好:私有构造函数限制了继承。
- 多线程问题:懒汉式需要特别注意线程安全。
- 常被认为是反模式(Anti-Pattern)或代码坏味(Code Smell),应谨慎使用。
- 适用场景:
- 需要严格控制实例数量的类,例如系统日志、配置信息类、线程池、计数器等。
- 需要全局访问的共享资源。
3.1原函数:
1 |
|
3.2单例模式改造
1 |
|
好的,我们来详细讲解一下设计模式中的策略模式 (Strategy Pattern)。
策略模式属于行为型模式 (Behavioral Pattern),它侧重于。
4.策略模式 (Strategy Pattern)
- 定义:策略模式定义了一系列算法,并将每一个算法封装起来,使它们可以相互替换。策略模式让算法的变化独立于使用算法的客户。
- 核心思想:将可能发生变化的行为(算法)抽象出来,定义一个统一的接口(策略接口),然后为每种具体的行为(算法)提供一个实现类(具体策略类)。在使用时,客户端(上下文)可以根据需要选择并注入具体的策略对象,从而动态地改变对象的行为。
- 解决的问题:
- 避免在一个类中使用大量的
if-else
或switch-case
语句来选择不同的行为逻辑,这使得代码难以维护和扩展。 - 当有多种算法或行为,并且它们之间可以互换时,使用策略模式可以更好地组织代码。
- 希望算法的实现细节对客户端透明。
- 避免在一个类中使用大量的
- 主要组成部分:
Context
(上下文):- 持有一个
Strategy
接口的引用。 - 通常有一个方法,该方法会调用
Strategy
对象的算法方法。 Context
不知道具体的策略实现,只与Strategy
接口交互。- 可以提供一个方法(如
setStrategy()
)来让客户端在运行时切换策略。
- 持有一个
Strategy
(策略接口或抽象类):- 定义了所有支持的算法的公共接口。
Context
使用这个接口来调用某个ConcreteStrategy
定义的算法。
ConcreteStrategy
(具体策略类):- 实现了
Strategy
接口。 - 封装了具体的算法或行为。
- 每个
ConcreteStrategy
代表一种特定的算法实现。
- 实现了
工作流程:
- 客户端决定使用哪种具体策略。
- 客户端创建一个具体策略 (
ConcreteStrategy
) 对象。 - 客户端将这个具体策略对象设置到上下文 (
Context
) 对象中。 - 当客户端请求上下文执行某个操作时,上下文会将请求委托给它所持有的策略对象的相应方法去执行。
优点:
- 遵循开闭原则:可以轻松地增加新的策略(算法),而无需修改上下文类或其他现有策略类。只需添加新的
ConcreteStrategy
实现即可。 - 避免多重条件语句:将
if-else
或switch
逻辑分散到各个策略类中,使得上下文类更简洁,职责更单一。 - 算法封装:每个算法都封装在独立的策略类中,易于理解、测试和维护。
- 策略切换灵活:客户端可以在运行时动态地改变上下文对象的策略(行为)。
- 复用性:策略类可以被多个上下文对象复用。
缺点:
- 类的数量增多:每增加一种策略就需要增加一个类,可能导致类的数量大幅增加。
- 客户端需要了解策略:客户端必须知道有哪些不同的策略,并需要自行决定在何时使用何种策略,然后将策略对象设置给上下文。(可以通过结合工厂模式等来隐藏具体策略类,减轻客户端的负担)。
- 上下文与策略间的通信:如果策略需要访问上下文的数据,可能需要将上下文对象(
this
)传递给策略,或者定义更复杂的数据传递接口,这可能导致一定的耦合。
适用场景:
- 当一个系统需要在多种算法中选择一种时,可以将这些算法封装成独立的策略类。
- 当一个对象有很多行为,并且这些行为使用
if-else
或switch
语句来选择时,可以考虑使用策略模式。 - 当一个算法的实现细节不希望暴露给客户端时。
- 当希望算法可以独立于使用它的客户端进行变化时。
- 需要动态地在几种算法中切换的场景。
4.1原代码
1 |
|
4.2策略模式改造
1 |
|
5.总结:
- 工厂模式:侧重于隐藏创建逻辑,根据需要返回不同类型的对象实例。
- 建造者模式:侧重于分步构建复杂对象,控制构建过程,得到不同表示的对象。
- 单例模式:侧重于保证全局唯一实例,并提供全局访问点。
- 策略模式:侧重于对象之间的职责分配和算法抽象封装与实例分离。
理解这些模式的核心意图和它们所解决的问题,有助于在实际开发中选择合适的模式来优化代码结构,提高代码的可维护性、可扩展性和可重用性。
Python Coding之设计模式
https://linxkon.github.io/python与设计模式.html