[Python設計模式] 第15章 如何兼容各種DB——抽象工廠模式
github地址:https://github.com/cheesezh/python_design_patterns
題目
如何讓一個程序,可以靈活替換數據庫?
基礎版本
class User(): """ 用戶類,模擬用戶表,假設只有ID和name兩個字段 """ def __init__(self): self.id = None self.name = None class SqlServerUser(): """ sqlserveruser類,用於操作User表 """ def insert(self, user): print("向SQL Server中添加一個User") def get_user(self, id): print("從SQL Server中搜索User", id) def main(): user = User() su = SqlServerUser() su.insert(user) su.get_user(1) main()
向SQL Server中添加一個User
從SQL Server中搜索User 1
點評
這裏之所以不能靈活更換數據庫,是因為su = SqlServerUser()
將客戶端和SQL Server綁定在一起,如果這裏是“多態的”,那麽就不需要考慮是SQL Server還是Access了。
這裏可以用“工廠方法模式”改進,工廠方法模式是定義一個用於創建對象的接口,讓子類決定實例化哪一個類。
改進版本1.0——工廠方法模式
from abc import ABCMeta, abstractmethod class IUser(): __metaclass__ = ABCMeta @abstractmethod def insert(self, user): pass @abstractmethod def get_user(self, id): pass class SqlServerUser(IUser): def insert(self, user): print("在SQL Server中添加一個User") def get_user(self, id): print("從SQL Server中搜索User", id) class AccessUser(IUser): def insert(self, user): print("在Access中添加一個User") def get_user(self, id): print("從Access中搜索User", id) class IFactory(): __metaclass__ = ABCMeta @abstractmethod def create_user(self): pass class SqlServerFactory(IFactory): def create_user(self): return SqlServerUser() class AccessFactory(IFactory): def create_user(self): return AccessUser() def main(): user = User() factory = SqlServerFactory() iuser = factory.create_user() iuser.insert(user) iuser.get_user(1) main()
在SQL Server中添加一個User
從SQL Server中搜索User 1
點評
現在如果要更換數據庫,只需要把factory = SqlServerFactory()
更改成factory = AccessFactory()
即可。這裏由於多態的關系,使得聲明IUser接口的對象iuser事先並不知道在訪問哪個數據庫,卻可以在運行時很好的完成工作,這就是業務邏輯與數據訪問解耦。
但是,數據庫中不可能只有一個User表,還可能有其他表,比如Department,那就需要增加好多個新的類。
class Department(): def __init__(self): self.id = None self.name = None class IDepartment(): __metaclass__ = ABCMeta @abstractmethod def insert(self, department): pass @abstractmethod def get_department(self, id): pass class SqlServerDepartment(IDepartment): def insert(self, department): print("在SQL Server中添加一個Department") def get_department(self, id): print("從SQL Server中搜索Department", id) class AccessDepartment(IDepartment): def insert(self, department): print("在Access中添加一個Department") def get_department(self, id): print("從Access中搜索Department", id) class IFactory(): __metaclass__ = ABCMeta @abstractmethod def create_user(self): pass @abstractmethod def create_department(self): pass class SqlServerFactory(IFactory): def create_user(self): return SqlServerUser() def create_department(self): return SqlServerDepartment() class AccessFactory(IFactory): def create_user(self): return AccessUser() def create_department(self): return AccessDepartment() def main(): user = User() dept = Department() factory = SqlServerFactory() iuser = factory.create_user() iuser.insert(user) iuser.get_user(1) idept = factory.create_department() idept.insert(dept) idept.get_department(1) main()
在SQL Server中添加一個User
從SQL Server中搜索User 1
在SQL Server中添加一個Department
從SQL Server中搜索Department 1
點評
這樣就可以做到,只需要更改factory = SqlServerFactory()
,就可以隨便切換數據庫了。
當只有一個User類和User操作類的時候,只需要工廠方法模式就可以了。但是數據庫中顯然有很多的表,而SQL Server和Acess又是兩大不同的類,所以解決這種涉及多個產品系列的問題,就需要使用抽象工廠模式。
抽象工廠模式
抽象工廠模式,提供一個創建一系列相關或相互依賴對象的接口,而無需指定它們的具體類。
在上述問題中:
- User和Department相當於兩個抽象產品;
- SqlServerUser和AccessUser是抽象產品User的具體產品實現;
- IFactory是一個抽象工廠接口,裏邊包含所有的產品創建的抽象方法;
- SqlServerFactory和AccessFactory是具體工廠;
通常的過程是,在運行時刻創建一個ConcretFactory類的實例,這個具體的工廠再創建具有特定實現的產品對象,也就是說,為創建不同的產品對象,客戶端應使用不同的具體工廠。
抽象工廠模式的優點是什麽?
最大的好處便是易於交換產品系列,由於具體工廠類在一個應用中只需要在初始化的時候出現一次,這就使得改變一個應用的具體工廠變得非常容易,它只需要改變具體工廠即可使用不同的產品配置。
其次的好處就是讓具體的創建實例過程與客戶端分離,客戶端是通過它們的抽象接口操縱實例,產品的具體類名也被具體工廠的實現分離,不會出現在客戶端代碼中。
抽象工廠模式的缺點是什麽?
抽象工廠模式可以很方便地切換兩個數據庫的訪問代碼,但是當需要增加功能,比如增加項目表Project,那就需要增加三個類IProject,SqlServerProject,AccessProject,還要更改IFactory,SqlServerFactory和AccessFactory。如果有100個調用數據訪問的類,那要更改100次才能切換數據庫,這是非常醜陋的做法。
用簡單工廠改進抽象工廠
去除IFactory,SqlServerFactory和AccessFactory三個工廠類,取而代之的是DataAccess類。
class DataAcess():
# 類變量,通過`類名.變量名`訪問
db = "sql_server"
@classmethod
def create_user(self):
if DataAcess.db == "sql_server":
return SqlServerUser()
elif DataAcess.db == "access":
return AccessUser()
@classmethod
def create_department(self):
if DataAcess.db == "sql_server":
return SqlServerDepartment()
elif DataAcess.db == "access":
return AccessDepartment()
def main():
user = User()
dept = Department()
iu = DataAcess.create_user()
iu.insert(user)
iu.get_user(1)
idept = DataAcess.create_department()
idept.insert(dept)
idept.get_department(1)
main()
在SQL Server中添加一個User
從SQL Server中搜索User 1
在SQL Server中添加一個Department
從SQL Server中搜索Department 1
點評
所有用到簡單工廠的地方,都可以考慮使用反射技術來去除swith或if-else,接觸分支帶來的耦合。
反射版本
import sys
def createInstance(module_name, class_name, *args, **kwargs):
class_meta = getattr(module_name, class_name)
obj = class_meta(*args, **kwargs)
return obj
def main():
db = "Access" # load from config file
user = User()
dept = Department()
iuser = createInstance(sys.modules[__name__], "{}User".format(db))
iuser.insert(user)
iuser.get_user(1)
idept = createInstance(sys.modules[__name__], "{}Department".format(db))
idept.insert(dept)
idept.get_department(1)
main()
在Access中添加一個User
從Access中搜索User 1
在Access中添加一個Department
從Access中搜索Department 1
[Python設計模式] 第15章 如何兼容各種DB——抽象工廠模式