Table of Contents
创建设计模式介绍
创建设计模式抽象了即时化过程,使系统独立于其对象的创建、构成和代表方式。在GOF模式中,Singleton和Factory Method是遇到次数最多的两个,但它们解决了根本不同的问题。Singleton控制了实例的数量,而Factory Method则将选择哪个混凝土类来即时化的责任赋予。如果错误应用任一模式,就会导致僵化、难以测试的代码或不必要的复杂。本条深入审查每一种模式,澄清其适当的背景,并为工程师在两者之间作出决定提供可操作的指导。
详细单子图案
单子图案将一个类限制在一个单一的案例中,并提供了一个全球访问该案例的点。 它是最简单的图案之一,但也因其对可检验性和耦合性的影响而成为争议最大的一个。
核心特征
- 单一实例担保: 私人建筑师防止外部即时化. 静态方法(通常)返回唯一实例.
- 全球访问: 实例从应用程序的任何地方都可以访问,通常通过公共静态变量或方法.
- 懒惰或急躁的初始化: 实例可以在类加载时间(eager)创建,或推迟到第一次请求(lazy)时才创建.
当Singleton合适时
- 必须协调的共享资源:配置管理器,线程池,连接池,日志服务,硬件接口驱动程序往往需要完全一个控制器.
- 不应重复的全局状态:缓存管理器,文件系统抽象层,或GUI框架中的窗口管理器.
- 资源密集型对象: 整个系统创建和再利用的昂贵对象从一个实例中受益.
执行情况考虑
线程安全是最常见的陷阱。 检查 [[FLT: 1]] 并创建实例的天真执行可以在多条环境中产生多个实例。 解决方案包括用 [[FLT: 2]] 进行双检查锁定, 静态内层类( Bill Pugh singleton) , 或在 Java 中一个以 enum 为基础的单子。 在 Python 中, 使用 [[FLT: 3]] 的线程安全初始化是标准。 急切和懒慢初始化之间的选择取决于单子是否被保证使用, 其创建是否重。
批评和陷阱
单子公司通常被认为是反标本公司,因为它们引入了全球状态,这使得单位测试变得难以依赖订单和难以隔离。它们也隐藏了依赖性;一个叫 的类直接与单子公司的混凝土类紧密结合。 现代实践建议使用依赖性注射作为共享的例子,允许在测试中用模拟来替代单子公司。 此外,分布式系统中的单子公司(如微服务)毫无意义,除非每个过程都具有范围——整个网络节点的单一例子需要额外的协调。
工厂方法模式细节
Factory Method 模式定义了创建对象的界面,但让子类决定哪个类进行即时处理,它将对象创建的责任从客户端转移到工厂方法,促进开放/封闭原则.
核心特征
- 封装创建逻辑:[ 客户端代码不知道混凝土类;它通过抽象的产品类型工作.
- 扩展性:[] 通过创建新的混凝土工厂,可以不修改现有的客户代码而添加新的产品类型.
- 被忽略的即时:[] 即时发生的确切类是在运行时间,根据输入,配置,或上下文确定.
工厂方法适当时
- 相关对象的家庭: 当一个系统需要工作时,需要使用多个产品变体,这些变体共享一个共同的界面——例如不同的数据库驱动程序,文档导出格式,或UI主题.
- 从具体执行中解析客户端代码:[客户端调用工厂方法,接收符合抽象界面的对象. 混凝土类的更改不影响客户端.
- 图形驱动创建:[] 应用程序可以在启动时根据配置文件,环境变量,或运行时条件决定使用哪一个混凝土厂.
执行情况考虑
典型的工厂方法使用一个抽象的类来宣告工厂方法(通常为抽象的). 具体创建者超越了这种方法来即时处理特定产品. 在语言中,没有继承(如JavaScript),工厂可以是功能或关闭. 模式与依赖性注射容器配合良好,可以替代执行. 一个常见的变体是Java中的静态工厂方法[(如)],但这与GoF工厂方法模式不同,它是一种简单的平面图案,不涉及子分类。
真实世界实例:文档转换器
考虑一种在格式之间转换文档的应用程序。 抽象的 [[FLT: 7]] 接口定义了一种[[FLT: 8] 方法。 厂家方法 [[FLT: 9]] 返回一个[[FLT: 10]] , [[FLT: 11]]], 或[[FLT: 12]]] , 以输入扩展为基础。 添加新的格式( 如Markdown) 只需要一个新的转换器类别, 并更新厂家方法—— 对转换管道没有更改 。
直接比较:Singleton对工厂方法
虽然两者都是创造模式,但其目标和权衡几乎是正统的。
| Aspect | Singleton | Factory Method |
|---|---|---|
| Primary goal | Ensure a single instance | Encapsulate object creation |
| Instance count | Exactly one | Many instances, but created through a factory |
| Control over class selection | Not relevant (always same class) | Subclasses or runtime logic choose the concrete class |
| Impact on maintainability | Can increase coupling (global access) | Reduces coupling (client depends on abstraction) |
| Testability | Often problematic (global state) | Good, as factories can be mocked |
| Extensibility | Limited (hard to subclass a singleton) | High (new products via new factories) |
当您最关心的事务是实例独具特色和全局协调时选择 Singleton , 例如, 记录服务必须序列化地写到一个文件。 当您专注于将对象创建与客户端代码脱钩并允许系统以新产品变体生长时, 请选择工厂方法, 例如, 一个需要在不同操作系统中制作本地按钮的图形用户界面工具箱 。
当它们重叠( 和何时使用均不使用)
通常,单子公司是工厂(例如,单子公司),它知道如何创造各种目标。 这种方法结合了两种模式,但继承了全球状态的缺点。 更好的办法是将工厂依赖性注入工厂,并保持工厂本身为普通的等级 — — 单子公司往往是工厂的错误选择。 如果目标是在整个应用中共享工厂实例,依赖性注射容器可以管理该实例的生命周期,而不会强制要求工厂实施单一子公司模式。
现代应用的实际考虑
测试和依赖性注射
两种模式都以不同的方式与测试相互作用。 单子测试在单位测试中都难于被取代。 一个常见的工作是引入单子的界面,并提供测试双倍,但这样做会破坏模式的简单性。 另一方面,工厂方法在测试中很容易被提供模拟工厂所取代。 在现代框架(春天、团结、吉斯)中,容器自动处理单子定义,从而不需要手动实施模式。
货币和分配系统
单子系统在分布式系统中崩溃,因为“单一实例”不能跨越多个进程或节点。 对于共享的微服务资源,工程师使用共享数据库、像Redis这样的缓存或领导者选举 — — 而不是单子系统模式 — — 即使分布式中仍然适用工厂方法;它只是在每个服务边界内创建对象。
结合现实世界解决方案的模式
许多生产系统将这些模式智能地结合在一起。例如,一个 Singleton连接池可能使用工厂方法来创建不同类型的连接(例如只读对读写). 单通确保每个应用程序有一个集合,而工厂方法则处理连接对象的创建。另一个例子是:一个单通 文件生成器[,它代表一个工厂方法来创建格式特定渲染器.
避免常见错误
- 当工厂足够时使用Singleton: 如果你仅仅因为性能原因想要一个单一的类,具有单吨范围的依赖性注射比全球存取器更清洁.
- 当对象创建为微不足道且固定时使用工厂方法:[ 如果对象类型从未改变过,也没有子类,则简单的构造器会更清晰.
- 工厂和产品家庭之间的紧密结合:[ 避免将配置或业务逻辑置于工厂方法内,而这种配置或逻辑应属于别处.
- 单顿中忘记线程安全:[在服务器环境中,一个非线程安全单顿可以在负载下产生腐败状态.
结论
Singleton和Factory Method在软件设计中起着根本不同的作用。Singleton执行一个单一的全球协调实例;Factory Metrology election 对象创建,以支持运行时间的可变性和可扩展性。在它们之间选择需要评估您的主要关注是实例独有性还是创建灵活性。两种模式都不是银弹,而是在可测试性、结合性和复杂性方面引入权衡。通过理解其优点和局限性,工程师可以有意地运用它们,往往与依赖性注入和现代框架相结合,来构建可伸缩和可维护的系统。
进一步阅读,请参看GOF经典模式上Reforming.Guru和 Factory Method. 也考虑马丁·福勒对 Registry[的分析,作为Singleton的替代,维基百科上关于 Factory Methody profy的文章,用于语言特定执行.