一文搞懂设计模式—工厂方法模式
本文已收录至 Github,推荐阅读 👉 Java随想录
微信公众号:Java随想录
在面向对象设计中,经常需要创建对象实例。传统的方式是在代码中直接使用 new
关键字来创建对象,但这种方式可能会导致高耦合和难以扩展。
工厂方法模式属于创建型模式,通过定义一个用于创建对象的接口,将具体的实例化延迟到子类中,提供了一种灵活、可扩展的对象创建方式,使得系统更加符合开闭原则。
使用场景
工厂方法模式适用于以下场景:
对象的创建过程比较复杂,包含一系列步骤或依赖关系,需要隐藏创建细节,只关注对象的使用。
需要在运行时动态决定创建哪个具体对象。
希望通过扩展工厂类来添加新的产品,而不是修改已有的代码。
一个常见的工厂方法模式在 Spring 中的应用例子是通过 FactoryBean
接口来创建自定义的工厂 Bean。
假设我们有一个名为 UserService
的服务类,它依赖于另一个名为 UserRepository
的数据访问对象。我们可以使用工厂方法模式来创建 UserService
实例,并将其作为一个 Bean 注册到 Spring 容器中。
首先,我们创建一个实现了 FactoryBean<UserService>
接口的工厂类 UserServiceFactory
:
在上述代码中,UserServiceFactory
实现了 FactoryBean<UserService>
接口,并重写了相关方法。在 getObject()
方法中,我们创建了一个 UserService
实例,并设置了其依赖的 UserRepository
。getObjectType()
方法返回了工厂创建的对象类型,isSingleton()
方法表示该工厂创建的对象是否为单例。
接下来,我们需要将 UserServiceFactory
和 UserRepository
注册到 Spring 容器中。可以通过 XML 配置文件进行配置:
在上述配置中,我们首先创建了一个 UserRepository
的 Bean,并将其注入到 UserServiceFactory
工厂类中。然后,通过 factory-bean
属性指定使用userServiceFactory
工厂来创建 userService
的实例。
这样,当 Spring 容器初始化时,会自动调用 UserServiceFactory
的 getObject()
方法来创建 UserService
实例,并将其作为一个 Bean 注册到容器中。可以通过 @Autowired
或其他方式来注入 UserService
对象,并使用它的服务。
通过这种方式,我们成功地应用了工厂方法模式,在 Spring 中管理和创建了 UserService
实例,并解耦了对象的创建和依赖注入过程。
具体实现
工厂方法模式涉及以下几个角色:
抽象产品(Abstract Product):定义了产品的抽象接口或抽象类,具体产品需要实现这个接口或继承这个抽象类。
具体产品(Concrete Product):实现了抽象产品定义的接口或继承抽象产品的抽象类,是工厂方法模式所创建的对象。
抽象工厂(Abstract Factory):定义了一个创建产品对象的抽象工厂接口,其中包含了创建产品的抽象方法。
具体工厂(Concrete Factory):实现了抽象工厂接口,负责创建具体的产品对象。具体工厂类通常含有与业务相关的逻辑,并在工厂方法中实例化具体产品对象。
在工厂方法模式中,抽象工厂和抽象产品是核心,而具体工厂和具体产品则根据实际需求进行扩展和实现。
通过这些角色的协作,工厂方法模式实现了将产品的创建过程封装起来,使得客户端与具体产品解耦,同时也提供了灵活性和可扩展性。
抽象产品类和具体产品类
首先定义一个抽象产品类 Product
:
然后创建具体产品类,如 ConcreteProductA
和 ConcreteProductB
,它们分别继承自 Product
并实现了其中的抽象方法。
抽象工厂类和具体工厂类
接下来定义一个抽象工厂类 Factory
,其中包含一个抽象的工厂方法 createProduct()
,用于创建具体的产品对象:
对于每个具体产品,创建相应的具体工厂类:
客户端代码
在客户端代码中,我们可以根据需要选择不同的具体工厂类来创建产品对象。
通过工厂方法模式,我们将对象的创建过程分散到不同的具体工厂类中,每个具体工厂类只负责创建对应的产品对象。这样可以降低代码的耦合度,同时也方便添加新的产品和工厂。
优点
符合开闭原则:工厂方法模式将产品的创建过程封装在具体工厂类中,新增产品时只需添加对应的工厂类,而无需修改已有的代码。
客户端与具体产品解耦:客户端代码只和抽象工厂类以及抽象产品类交互,无需关心具体的实现细节,从而实现了高层模块和底层模块的解耦。
扩展性好:通过添加新的具体工厂类和具体产品类,可以灵活地扩展系统,符合开放封闭原则。
容易进行单元测试:由于工厂方法模式将对象的创建过程封装到具体工厂类中,我们可以轻松地替换具体工厂类来进行单元测试,提高代码的可测试性。
缺点
类的数量增加:引入工厂方法模式会增加类的数量,增加了系统的复杂度。
增加了系统的抽象性和理解难度:相比于简单工厂模式,工厂方法模式引入了更多的抽象类和接口,对于初学者来说可能更难理解。
注意:工厂方法模式适合复杂对象,而简单对象,特别是只需要通过 new 就可以完成创建的对象,无需使用工厂模式。如果使用工厂模式,就需要引入一个工厂类,会增加系统的复杂度。
简单工厂模式
当只有少量具体产品类时,并且对象的创建逻辑相对简单,没有必要为每个具体产品类创建一个对应的工厂类,此时使用简单工厂模式会更加简洁和直观。
简单工厂模式(Simple Factory Pattern)是工厂方法模式的弱化。
简单工厂模式由三个主要角色组成:
工厂类(Factory Class):负责创建对象的核心类,它通常包含一个静态方法或者非静态方法,根据客户端传入的参数来创建相应的对象实例。
抽象产品类(Abstract Product Class):定义了具体产品类的共同接口或抽象类,描述了产品的通用行为。
具体产品类(Concrete Product Class):实现了抽象产品类所定义的接口或抽象类,具体产品类是工厂类所创建的目标对象。
下面是一个简单的示例代码,演示了简单工厂模式的实现:
在上述代码中,我们定义了一个抽象产品类 Animal
,并有两个具体产品类 Cat
和 Dog
,它们都实现了 Animal
接口。工厂类 AnimalFactory
负责根据客户端传入的参数创建相应的具体产品对象。
使用简单工厂模式,客户端可以通过调用工厂类的静态方法 createAnimal()
来获取所需的具体产品对象。例如:
简单工厂模式因为工厂类定义了一个静态方法,因此也叫做静态工厂模式。其缺点是工厂类的扩展比较困难,不符合开闭原则,并且随着产品类型增多,简单工厂模式工厂类的代码可能会变得复杂,因此不适用于大规模或复杂的应用程序,但它仍然是一个非常实用的设计模式。
延迟初始化
延迟初始化:一个对象被消费完毕后,并不立刻释放,工厂类保持其初始状态,等待再次被使用。
延迟加载的工厂类,参考代码如下:
代码算是比较简单,通过定义一个 Map 容器,容纳所有产生的对象,如果在 Map 容器中已经有的对象,则直接取出返回;如果没有,则根据需要的类型产生一个对象并放入到 Map 容器中,以方便下次调用。
这样的好处是可以限制某一个产品类的最大实例化数量,通过判断 Map 中已有的对象数量来实现。
延迟加载在对象初始化比较复杂的情况下,可以降低对象的产生和销毁带来的复杂性。这是非常有意义的,例如 JDBC 连接数据库,都会要求设置一个 MaxConnections 最大连接数量,该数量就是内存中最大实例化的数量。
总结
工厂方法模式使用的频率非常高,工厂方法模式通过定义抽象工厂类和抽象产品类,将对象的创建委托给子类来实现。它提供了一种灵活、可扩展的对象创建方式,符合开闭原则,并且降低了代码的耦合度。
通过合理地使用工厂方法模式,我们可以提高代码的灵活性、可扩展性和可维护性,从而构建更优秀的软件系统。
版权声明: 本文为 InfoQ 作者【码农BookSea】的原创文章。
原文链接:【http://xie.infoq.cn/article/a843801220caef14b2ee5e115】。文章转载请联系作者。
评论