在面向对象设计中,经常需要创建对象实例。传统的方式是在代码中直接使用 new 关键字来创建对象,但这种方式可能会导致高耦合和难以扩展。
工厂方法模式属于创建型模式,通过定义一个用于创建对象的接口,将具体的实例化延迟到子类中,提供了一种灵活、可扩展的对象创建方式,使得系统更加符合开闭原则。
使用场景
工厂方法模式适用于以下场景:
- 对象的创建过程比较复杂,包含一系列步骤或依赖关系,需要隐藏创建细节,只关注对象的使用。
- 需要在运行时动态决定创建哪个具体对象。
- 希望通过扩展工厂类来添加新的产品,而不是修改已有的代码。
一个常见的工厂方法模式在 Spring 中的应用例子是通过 FactoryBean 接口来创建自定义的工厂 Bean。
假设我们有一个名为 UserService 的服务类,它依赖于另一个名为 UserRepository 的数据访问对象。我们可以使用工厂方法模式来创建 UserService 实例,并将其作为一个 Bean 注册到 Spring 容器中。
首先,我们创建一个实现了 FactoryBean 接口的工厂类 UserServiceFactory:
public class UserServiceFactory implements FactoryBean {
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 接口,并重写了相关方法。在 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:
public abstract class Product {
public abstract void use();
}
然后创建具体产品类,如 ConcreteProductA 和 ConcreteProductB,它们分别继承自 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,并有两个具体产品类 Cat 和 Dog,它们都实现了 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 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 最大连接数量,该数量就是内存中最大实例化的数量。
总结
工厂方法模式使用的频率非常高,工厂方法模式通过定义抽象工厂类和抽象产品类,将对象的创建委托给子类来实现。它提供了一种灵活、可扩展的对象创建方式,符合开闭原则,并且降低了代码的耦合度。
通过合理地使用工厂方法模式,我们可以提高代码的灵活性、可扩展性和可维护性,从而构建更优秀的软件系统。