1. 程式人生 > >[Python設計模式] 第15章 如何兼容各種DB——抽象工廠模式

[Python設計模式] 第15章 如何兼容各種DB——抽象工廠模式

for from code trac 抽象接口 pass 工廠 https reat

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——抽象工廠模式