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

212 阅读9分钟

本文已收录至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();
}

// 具体产品类1
public class Cat implements Animal {
    @Override
    public void speak() {
        System.out.println("Meow!");
    }
}

// 具体产品类2
public 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 最大连接数量,该数量就是内存中最大实例化的数量。

总结

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

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