写点什么

一文搞懂设计模式—工厂方法模式

作者:码农BookSea
  • 2024-02-20
    浙江
  • 本文字数:4613 字

    阅读完需:约 15 分钟

一文搞懂设计模式—工厂方法模式

本文已收录至 Github,推荐阅读 👉 Java随想录

微信公众号:Java随想录


在面向对象设计中,经常需要创建对象实例。传统的方式是在代码中直接使用 new 关键字来创建对象,但这种方式可能会导致高耦合和难以扩展。


工厂方法模式属于创建型模式,通过定义一个用于创建对象的接口,将具体的实例化延迟到子类中,提供了一种灵活、可扩展的对象创建方式,使得系统更加符合开闭原则。

使用场景

工厂方法模式适用于以下场景:


  • 对象的创建过程比较复杂,包含一系列步骤或依赖关系,需要隐藏创建细节,只关注对象的使用。

  • 需要在运行时动态决定创建哪个具体对象。

  • 希望通过扩展工厂类来添加新的产品,而不是修改已有的代码。


一个常见的工厂方法模式在 Spring 中的应用例子是通过 FactoryBean 接口来创建自定义的工厂 Bean。


假设我们有一个名为 UserService 的服务类,它依赖于另一个名为 UserRepository 的数据访问对象。我们可以使用工厂方法模式来创建 UserService 实例,并将其作为一个 Bean 注册到 Spring 容器中。


首先,我们创建一个实现了 FactoryBean<UserService> 接口的工厂类 UserServiceFactory


public class UserServiceFactory implements FactoryBean<UserService> {    private UserRepository userRepository;
public void setUserRepository(UserRepository userRepository) { this.userRepository = userRepository; }
@Override public UserService getObject() throws Exception { UserService userService = new UserService(); userService.setUserRepository(userRepository); return userService; }
@Override public Class<?> getObjectType() { return UserService.class; }
@Override public boolean isSingleton() { return true; }}
复制代码


在上述代码中,UserServiceFactory 实现了 FactoryBean<UserService> 接口,并重写了相关方法。在 getObject() 方法中,我们创建了一个 UserService实例,并设置了其依赖的 UserRepositorygetObjectType() 方法返回了工厂创建的对象类型,isSingleton() 方法表示该工厂创建的对象是否为单例。


接下来,我们需要将 UserServiceFactoryUserRepository 注册到 Spring 容器中。可以通过 XML 配置文件进行配置:


<bean id="userRepository" class="com.example.UserRepository"/>
<bean id="userServiceFactory" class="com.example.UserServiceFactory"> <property name="userRepository" ref="userRepository"/></bean>
<bean id="userService" factory-bean="userServiceFactory" factory-method="getObject"/>
复制代码


在上述配置中,我们首先创建了一个 UserRepository 的 Bean,并将其注入到 UserServiceFactory 工厂类中。然后,通过 factory-bean 属性指定使用userServiceFactory 工厂来创建 userService 的实例。


这样,当 Spring 容器初始化时,会自动调用 UserServiceFactorygetObject() 方法来创建 UserService 实例,并将其作为一个 Bean 注册到容器中。可以通过 @Autowired 或其他方式来注入 UserService 对象,并使用它的服务。


通过这种方式,我们成功地应用了工厂方法模式,在 Spring 中管理和创建了 UserService 实例,并解耦了对象的创建和依赖注入过程。

具体实现

工厂方法模式涉及以下几个角色:


  • 抽象产品(Abstract Product):定义了产品的抽象接口或抽象类,具体产品需要实现这个接口或继承这个抽象类。

  • 具体产品(Concrete Product):实现了抽象产品定义的接口或继承抽象产品的抽象类,是工厂方法模式所创建的对象。

  • 抽象工厂(Abstract Factory):定义了一个创建产品对象的抽象工厂接口,其中包含了创建产品的抽象方法。

  • 具体工厂(Concrete Factory):实现了抽象工厂接口,负责创建具体的产品对象。具体工厂类通常含有与业务相关的逻辑,并在工厂方法中实例化具体产品对象。


在工厂方法模式中,抽象工厂和抽象产品是核心,而具体工厂和具体产品则根据实际需求进行扩展和实现。


通过这些角色的协作,工厂方法模式实现了将产品的创建过程封装起来,使得客户端与具体产品解耦,同时也提供了灵活性和可扩展性。


抽象产品类和具体产品类


首先定义一个抽象产品类 Product


public abstract class Product {    public abstract void use();}
复制代码


然后创建具体产品类,如 ConcreteProductAConcreteProductB,它们分别继承自 Product 并实现了其中的抽象方法。


public class ConcreteProductA  extends Product {        @Override    public void use(){       System.out.println("use ConcreteProductA");        }}
复制代码


public class ConcreteProductB  extends Product {    @Override    public void use(){        System.out.println("use ConcreteProductB");         }}
复制代码


抽象工厂类和具体工厂类


接下来定义一个抽象工厂类 Factory,其中包含一个抽象的工厂方法 createProduct(),用于创建具体的产品对象:


public abstract class Factory {    public abstract Product createProduct();}
复制代码


对于每个具体产品,创建相应的具体工厂类:


public class ConcreteFactoryA extends Factory {        @Override    public Product createProduct() {        return new ConcreteProductA();    }}
复制代码


public class ConcreteFactoryB extends Factory {        @Override    public Product createProduct() {        return new ConcreteProductB();    }}
复制代码


客户端代码


在客户端代码中,我们可以根据需要选择不同的具体工厂类来创建产品对象。


public class Client {    public static void main(String[] args) {        Factory factory = new ConcreteFactoryA();        Product product = factory.createProduct();        product.use();    }}
复制代码


通过工厂方法模式,我们将对象的创建过程分散到不同的具体工厂类中,每个具体工厂类只负责创建对应的产品对象。这样可以降低代码的耦合度,同时也方便添加新的产品和工厂。


优点


  • 符合开闭原则:工厂方法模式将产品的创建过程封装在具体工厂类中,新增产品时只需添加对应的工厂类,而无需修改已有的代码。

  • 客户端与具体产品解耦:客户端代码只和抽象工厂类以及抽象产品类交互,无需关心具体的实现细节,从而实现了高层模块和底层模块的解耦。

  • 扩展性好:通过添加新的具体工厂类和具体产品类,可以灵活地扩展系统,符合开放封闭原则。

  • 容易进行单元测试:由于工厂方法模式将对象的创建过程封装到具体工厂类中,我们可以轻松地替换具体工厂类来进行单元测试,提高代码的可测试性。


缺点


  • 类的数量增加:引入工厂方法模式会增加类的数量,增加了系统的复杂度。

  • 增加了系统的抽象性和理解难度:相比于简单工厂模式,工厂方法模式引入了更多的抽象类和接口,对于初学者来说可能更难理解。


注意:工厂方法模式适合复杂对象,而简单对象,特别是只需要通过 new 就可以完成创建的对象,无需使用工厂模式。如果使用工厂模式,就需要引入一个工厂类,会增加系统的复杂度。

简单工厂模式

当只有少量具体产品类时,并且对象的创建逻辑相对简单,没有必要为每个具体产品类创建一个对应的工厂类,此时使用简单工厂模式会更加简洁和直观。


简单工厂模式(Simple Factory Pattern)是工厂方法模式的弱化。


简单工厂模式由三个主要角色组成:


  • 工厂类(Factory Class):负责创建对象的核心类,它通常包含一个静态方法或者非静态方法,根据客户端传入的参数来创建相应的对象实例。

  • 抽象产品类(Abstract Product Class):定义了具体产品类的共同接口或抽象类,描述了产品的通用行为。

  • 具体产品类(Concrete Product Class):实现了抽象产品类所定义的接口或抽象类,具体产品类是工厂类所创建的目标对象。


下面是一个简单的示例代码,演示了简单工厂模式的实现:


// 抽象产品类public interface Animal {    void speak();}
// 具体产品类1public class Cat implements Animal { @Override public void speak() { System.out.println("Meow!"); }}
// 具体产品类2public class Dog implements Animal { @Override public void speak() { System.out.println("Woof!"); }}
// 工厂类public class AnimalFactory { public static Animal createAnimal(String type) { if (type.equalsIgnoreCase("cat")) { return new Cat(); } else if (type.equalsIgnoreCase("dog")) { return new Dog(); } throw new IllegalArgumentException("Invalid animal type: " + type); }}
复制代码


在上述代码中,我们定义了一个抽象产品类 Animal,并有两个具体产品类 CatDog,它们都实现了 Animal 接口。工厂类 AnimalFactory 负责根据客户端传入的参数创建相应的具体产品对象。


使用简单工厂模式,客户端可以通过调用工厂类的静态方法 createAnimal() 来获取所需的具体产品对象。例如:


Animal cat = AnimalFactory.createAnimal("cat");cat.speak();  // 输出:Meow!
Animal dog = AnimalFactory.createAnimal("dog");dog.speak(); // 输出:Woof!
复制代码


简单工厂模式因为工厂类定义了一个静态方法,因此也叫做静态工厂模式。其缺点是工厂类的扩展比较困难,不符合开闭原则,并且随着产品类型增多,简单工厂模式工厂类的代码可能会变得复杂,因此不适用于大规模或复杂的应用程序,但它仍然是一个非常实用的设计模式。

延迟初始化

延迟初始化:一个对象被消费完毕后,并不立刻释放,工厂类保持其初始状态,等待再次被使用。


延迟加载的工厂类,参考代码如下:


public class ProductFactory {    private static final Map<String, Product> prMap = new HashMap();
public static synchronized Product createProduct(String type) throws Exception { Product product = null; //如果Map中已经有这个对象 if (prMap.containsKey(type)) { product = prMap.get(type); } else { if (type.equals("Product1")) { product = new ConcreteProduct1(); } else { product = new ConcreteProduct2(); } //同时把对象放到缓存容器中 prMap.put(type, product); } return product; }}
复制代码


代码算是比较简单,通过定义一个 Map 容器,容纳所有产生的对象,如果在 Map 容器中已经有的对象,则直接取出返回;如果没有,则根据需要的类型产生一个对象并放入到 Map 容器中,以方便下次调用。


这样的好处是可以限制某一个产品类的最大实例化数量,通过判断 Map 中已有的对象数量来实现。


延迟加载在对象初始化比较复杂的情况下,可以降低对象的产生和销毁带来的复杂性。这是非常有意义的,例如 JDBC 连接数据库,都会要求设置一个 MaxConnections 最大连接数量,该数量就是内存中最大实例化的数量。

总结

工厂方法模式使用的频率非常高,工厂方法模式通过定义抽象工厂类和抽象产品类,将对象的创建委托给子类来实现。它提供了一种灵活、可扩展的对象创建方式,符合开闭原则,并且降低了代码的耦合度。


通过合理地使用工厂方法模式,我们可以提高代码的灵活性、可扩展性和可维护性,从而构建更优秀的软件系统。

发布于: 刚刚阅读数: 3
用户头像

码农BookSea

关注

Java开发工程师 2021-12-26 加入

Java开发菜鸟工程师,写博客的初衷是为了沉淀我所学习,累积我所见闻,分享我所体验。希望和更多的人交流学习。

评论

发布
暂无评论
一文搞懂设计模式—工厂方法模式_Java_码农BookSea_InfoQ写作社区