一文搞懂設計模式—工廠方法模式
在面向對象設計中,經常需要創建對象實例。傳統的方式是在代碼中直接使用 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實例,并設置了其依賴的 UserRepository。getObjectType() 方法返回了工廠創建的對象類型,isSingleton() 方法表示該工廠創建的對象是否為單例。
接下來,我們需要將 UserServiceFactory 和 UserRepository 注冊到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容器初始化時,會自動調用 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<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 最大連接數量,該數量就是內存中最大實例化的數量。
總結
工廠方法模式使用的頻率非常高,工廠方法模式通過定義抽象工廠類和抽象產品類,將對象的創建委托給子類來實現。它提供了一種靈活、可擴展的對象創建方式,符合開閉原則,并且降低了代碼的耦合度。
通過合理地使用工廠方法模式,我們可以提高代碼的靈活性、可擴展性和可維護性,從而構建更優秀的軟件系統。