第15章:抽象工廠模式
抽象工廠模式
工廠方法模式(factory method):定義一個用於創建對象的接口,讓子類決定實例化哪一個類。工廠方法使一個類的實例化延遲到其子類。
抽象工廠模式(abstract factory):提供一個創建一系列相關或相互依賴對象的接口,而無需指定它們具體的類。
AbstractProductA
和AbstractProductB
是兩個抽象產品,之所以爲抽象,是因爲它們都有可能有兩種不同的實現,而ProductA1
、ProductA2
和ProductB1
、ProductB2
就是對兩個抽象產品的具體分類的實現。
IFactory
是一個抽象工廠接口,它裏面應該包含所有的產品創建的抽象方法。ConcreteFactory1
和ConcreteFactory2
是具體的工廠。
通常是在運行時刻再創建一個ConcreteFactory
類的實例,這個具體的工廠再創建具有特定實現的產品對象,即,爲創建不同的產品對象,客戶端應使用不同的具體工廠。
抽象工廠模式的優點與缺點
抽象工廠模式的優點:
-
易於交換產品系列,由於具體工廠類在一個應用中只需要在初始化的時候出現一次,這就使得改變一個應用的具體工廠變得非常容易,它只需要改變具體工廠即可使用不同的產品配置;
-
讓具體的創建實例過程與客戶端分離,客戶端是通過它們的抽象接口操縱實例,產品的具體類名也被具體工廠的實現分離,不會出現在客戶代碼中。
抽象工廠模式的缺點:
-
不適於增加功能;
-
當存在多個客戶端程序類時,需要在每個客戶端中都做一個工廠聲明。
抽象工廠模式示例
任務:數據庫中創建、查詢用戶
- 工廠模式
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
類取代IFactory
、SqlServerFactory
、AccessFactory
三個工廠類。
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
類,用反射技術,取代IFactory
、SqlServerFactory
、AccessFactory
三個工廠類。
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
的賦值代碼:
所有在用簡單工廠的地方,都可以考慮用反射技術來去除switch
或if
,解除分支判斷帶來的耦合。
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表一條記錄