• [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
  • 相关阅读:
    由"跨域"引出的一个终极思想(jsonp)
    SQLAlchemy 使用教程
    rbac-基于角色的权限控制系统(8种常用场景再现)
    Django中间件 (middleware)
    tcp粘包问题原因及解决办法
    细说【json&pickle】dumps,loads,dump,load的区别
    python面向对象--快速入门
    python三大器(装饰器/生成器/迭代器)
    django神器 <自定义过滤器filter 和 标签tag>
    python 基础数据类型汇总
  • 原文地址:https://www.cnblogs.com/CheeseZH/p/9471431.html
Copyright © 2020-2023  润新知