[Python设计模式] 第15章 如何兼容各种DB——抽象工厂模式

Posted ZH奶酪(张贺)

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了[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——抽象工厂模式的主要内容,如果未能解决你的问题,请参考以下文章

[Python设计模式] 第17章 程序中的翻译官——适配器模式

《软件构架实践》13-15章读后感

第33章 项目实战-兼容式响应布局1

《python解释器源码剖析》第15章--python模块的动态加载机制

[Python设计模式] 第9章 如何准备多份简历——原型模式

python编程快速上手第7章习题20