大話設計模式:第15章 抽象工廠模式

第15章:抽象工廠模式

抽象工廠模式

工廠方法模式(factory method):定義一個用於創建對象的接口,讓子類決定實例化哪一個類。工廠方法使一個類的實例化延遲到其子類。

抽象工廠模式(abstract factory):提供一個創建一系列相關或相互依賴對象的接口,而無需指定它們具體的類。

在這裏插入圖片描述

AbstractProductAAbstractProductB是兩個抽象產品,之所以爲抽象,是因爲它們都有可能有兩種不同的實現,而ProductA1ProductA2ProductB1ProductB2就是對兩個抽象產品的具體分類的實現。

IFactory是一個抽象工廠接口,它裏面應該包含所有的產品創建的抽象方法。ConcreteFactory1ConcreteFactory2是具體的工廠。

通常是在運行時刻再創建一個ConcreteFactory類的實例,這個具體的工廠再創建具有特定實現的產品對象,即,爲創建不同的產品對象,客戶端應使用不同的具體工廠。

抽象工廠模式的優點與缺點

抽象工廠模式的優點

  1. 易於交換產品系列,由於具體工廠類在一個應用中只需要在初始化的時候出現一次,這就使得改變一個應用的具體工廠變得非常容易,它只需要改變具體工廠即可使用不同的產品配置;

  2. 讓具體的創建實例過程與客戶端分離,客戶端是通過它們的抽象接口操縱實例,產品的具體類名也被具體工廠的實現分離,不會出現在客戶代碼中。

抽象工廠模式的缺點

  1. 不適於增加功能;

  2. 當存在多個客戶端程序類時,需要在每個客戶端中都做一個工廠聲明。

抽象工廠模式示例

任務:數據庫中創建、查詢用戶

  • 工廠模式

在這裏插入圖片描述

from abc import ABC, abstractmethod
from typing import Text
class User(object):
    
    def __init__(self):
        self.__id = None
        self.__name = None
        
    @property
    def id(self) -> int:
        return self.__id
    @id.setter
    def id(self, value: int) -> None:
        self.__id = value
        
    @property
    def name(self) -> Text:
        return self.__name
    @name.setter
    def name(self, value: Text) -> None:
        self.__name = value
class IUser(ABC):
    """
    IUser接口,用於客戶端訪問,解除與具體數據庫訪問的耦合。
    """
    @abstractmethod
    def insert(user: User) -> None:
        pass
    
    @abstractmethod
    def get_user(id: int) -> User:
        pass
    
class SqlServerUser(IUser):
    """
    SqlServerUser類,用於訪問SQL Server的User。
    """
    def insert(self, user: User) -> None:
        print("在SQL Server中給User表增加一條記錄")
        
    def get_user(self, id: int) -> None:
        print("在SQL Server中根據ID得到User表一條記錄")
        return None
    
class AccessUser(IUser):
    """
    AccessUser類,用於訪問Access的User。
    """
    def insert(self, user: User) -> None:
        print("在Access中給User表增加一條記錄")
        
    def get_user(self, id: int) -> None:
        print("在Access中根據ID得到User表一條記錄")
        return None
    
class IFactory(ABC):
    """
    IFactory接口,定義一個創建訪問User表對象的抽象的工廠接口。
    """
    @abstractmethod
    def create_user(self) -> IUser:
        pass
class SqlServerFactory(IFactory):
    """
    SqlServerFactory類,實現IFactory接口,實例化SqlServerUser。
    """
    def create_user(self) -> IUser:
        return SqlServerUser()
    
class AccessFactory(IFactory):
    """
    AccessFactory,實現IFactory接口,實例化AccessUser。
    """
    def create_user(self) -> IUser:
        return AccessUser()
# 客戶端代碼

if __name__ == "__main__":
    
    user = User()
    
    # sql server
    factory = SqlServerFactory()
    iu = factory.create_user()
    
    iu.insert(user)
    iu.get_user(1)
    
    # access
    factory = AccessFactory()
    iu = factory.create_user()
    
    iu.insert(user)
    iu.get_user(1)
在SQL Server中給User表增加一條記錄
在SQL Server中根據ID得到User表一條記錄
在Access中給User表增加一條記錄
在Access中根據ID得到User表一條記錄

任務:數據庫中創建、查詢用戶,並增加部門表

  • 抽象工廠模式

在這裏插入圖片描述

class Department(object):
    
    def __init__(self):
        self.__id = None
        self.__name = None
        
    @property
    def id(self) -> int:
        return self.__id
    @id.setter
    def id(self, value: int) -> None:
        self.__id = value
        
    @property
    def name(self) -> Text:
        return self.__name
    @name.setter
    def name(self, value: Text) -> None:
        self.__name = value
class IDepartment(ABC):
    """
    IDepartment,用於客戶端訪問,解除與具體數據庫訪問的耦合。
    """
    
    @abstractmethod
    def insert(self, department: Department) -> None:
        pass
    
    @abstractmethod
    def get_department(self, id: int) -> Department:
        pass
class SqlServerDepartment(IDepartment):
    """
    SqlServerUser類,用於訪問SQL Server的Department。
    """
    def insert(self, department: Department) -> None:
        print("在SQL Server中給Department表增加一條記錄")
        
    def get_department(self, id: int) -> None:
        print("在SQL Server中根據ID得到Department表一條記錄")
        return None
    
class AccessDepartment(IDepartment):
    """
    AccessUser類,用於訪問Access的Department。
    """
    def insert(self, department: Department) -> None:
        print("在Access中給Department表增加一條記錄")
        
    def get_department(self, id: int) -> None:
        print("在Access中根據ID得到Department表一條記錄")
        return None
    
class IFactory(ABC):
    """
    IFactory接口,定義一個創建訪問User、Department表對象的抽象的工廠接口。
    """
    @abstractmethod
    def create_user(self) -> IUser:
        pass
    
    @abstractmethod
    def create_department(self) -> IDepartment:
        pass
class SqlServerFactory(IFactory):
    """
    SqlServerFactory類,實現IFactory接口,實例化SqlServerUser和SqlServerDepartment。
    """
    def create_user(self) -> IUser:
        return SqlServerUser()
    
    def create_department(self) -> IDepartment:
        return SqlServerDepartment()
    
class AccessFactory(IFactory):
    """
    AccessFactory,實現IFactory接口,實例化AccessUser和AccessDepartment。
    """
    def create_user(self) -> IUser:
        return AccessUser()
    
    def create_department(self) -> IDepartment:
        return AccessDepartment()

# 客戶端代碼

if __name__ == "__main__":
    
    user = User()
    dept = Department()
    
    # sql server
    factory = SqlServerFactory()
    # access
    # factory = AccessFactory()
    
    iu = factory.create_user()
    iu.insert(user)
    iu.get_user(1)
    
    id_ = factory.create_department()
    id_.insert(dept)
    id_.get_department(1)
在SQL Server中給User表增加一條記錄
在SQL Server中根據ID得到User表一條記錄
在SQL Server中給Department表增加一條記錄
在SQL Server中根據ID得到Department表一條記錄

用簡單工廠改進抽象工廠

用簡單工廠改進抽象工廠:用DataAccess類取代IFactorySqlServerFactoryAccessFactory三個工廠類。

在這裏插入圖片描述

class DataAccess(object):
    
    # 數據庫名稱
    __db = "SqlServer"
    # __db = "Access"
    
    __switch_user = {
        "SqlServer": SqlServerUser(),
        "Access": AccessUser()
    }
    
    __switch_department = {
        "SqlServer": SqlServerDepartment(),
        "Access": AccessDepartment()
    }
    
    def __init__(self) -> None:
        pass
    
    @classmethod
    def create_user(cls) -> IUser:
        return cls.__switch_user[cls.__db]
    
    @classmethod
    def create_department(cls) -> IDepartment:
        return cls.__switch_department[cls.__db]
        
# 客戶端代碼

if __name__ == "__main__":
    user = User()
    dept = Department()
    
    iu = DataAccess.create_user()
    iu.insert(user)
    iu.get_user(1)
    
    id_ = DataAccess.create_department()
    id_.insert(dept)
    id_.get_department(1)
    
    
在SQL Server中給User表增加一條記錄
在SQL Server中根據ID得到User表一條記錄
在SQL Server中給Department表增加一條記錄
在SQL Server中根據ID得到Department表一條記錄

用反射+抽象工廠的數據訪問程序

依賴注入(dependency injection):根據字符串的值去某個地方查找需要實例化的類。依賴注入需要專門的IoC容器提供,如Spring.Net,本例中可以通過.Net技術反射實現。反射格式:

Assembly.Load("程序集名稱").CreateInstance("命名空間.類名稱")

使用反射可以克服抽象工廠模式的先天不足。

任務:數據庫中創建、查詢用戶,並增加部門表;不希望使用switch

獲取實例的方法:

在這裏插入圖片描述

反射將程序由編譯時轉爲運行時。由於CreateInstance("抽象工廠模式.SqlserverUser")中的字符串是可以寫成變量,而變最的取值是SQL Server還是Access可以由變量db決定,所以去除了switch判斷。

在這裏插入圖片描述
DataAccess類,用反射技術,取代IFactorySqlServerFactoryAccessFactory三個工廠類。

from importlib import import_module
class DataAccess(object):
    
    # 程序集(包)名稱
    __package_name = "抽象工廠模式"
    # 模塊名稱
    __module_name = "抽象工廠模式"
    # 數據庫名稱
    __db = "SqlServer"
    
    def __init__(self) -> None:
        pass
    
    @classmethod
    def create_user(cls) -> IUser:
        module = import_module(cls.__package_name + "." + cls.__module_name)
        user = getattr(module, cls.__db + "User")
        
        return user()
    
    @classmethod
    def create_department(cls) -> IUser:
        module = import_module(cls.__package_name + "." + cls.__module_name)
        department = getattr(module, cls.__db + "Department")
        
        return department()

if __name__ == "__main__":
    
    user = DataAccess.create_user()
    dept = DataAccess.create_department()
    
    iu = DataAccess.create_user()
    iu.insert(user)
    iu.get_user(1)
    
    id_ = DataAccess.create_department()
    id_.insert(dept)
    id_.get_department(1)
 
在SQL Server中給User表增加一條記錄
在SQL Server中根據ID得到User表一條記錄
在SQL Server中給Department表增加一條記錄
在SQL Server中根據ID得到Department表一條記錄

用反射+配置文件實現數據訪問程序

任務:數據庫中創建、查詢用戶,並增加部門表;利用配置文件來解決更改DataAccess的問題。

配置文件App.config

在這裏插入圖片描述

引用System.configuratio15.,程序開關增加using System.Configuartion,然後更改DataAccess類的字段DB的賦值代碼:

在這裏插入圖片描述

所有在用簡單工廠的地方,都可以考慮用反射技術來去除switchif,解除分支判斷帶來的耦合。

from importlib import import_module
import os
try:
    import xml.etree.cElementTree as ET
except ImportError:
    import xml.etree.ElementTree as ET

class DataAccess(object):
    
    # 程序集(包)名稱
    __package_name = "抽象工廠模式"
    # 模塊名稱
    __module_name = "抽象工廠模式"
    # 數據庫名稱
    __db = None
    
    def __init__(self) -> None:
        pass
    
    @classmethod
    def load_config(cls, config: Text) -> None:
        tree = ET.parse(os.path.join(".", cls.__package_name, config))
        root = tree.getroot()
        app_settings = root.find("appSettings")
        for add in app_settings.findall("add"):
            if add.get("key") == "DB":
                cls.__db = add.get("value")
    
    @classmethod
    def create_user(cls) -> IUser:
        module = import_module(cls.__package_name + "." + cls.__module_name)
        user = getattr(module, cls.__db + "User")
        
        return user()
    
    @classmethod
    def create_department(cls) -> IUser:
        module = import_module(cls.__package_name + "." + cls.__module_name)
        department = getattr(module, cls.__db + "Department")
        
        return department()

if __name__ == "__main__":
    DataAccess.load_config("app.config")
    user = DataAccess.create_user()
    dept = DataAccess.create_department()
    
    iu = DataAccess.create_user()
    iu.insert(user)
    iu.get_user(1)
    
    id_ = DataAccess.create_department()
    id_.insert(dept)
    id_.get_department(1)
 
在Access中給User表增加一條記錄
在Access中根據ID得到User表一條記錄
在Access中給Department表增加一條記錄
在Access中根據ID得到Department表一條記錄
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章