github地址:
题目
如何让一个程序,可以灵活替换数据库?
基础版本
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, abstractmethodclass 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 sysdef createInstance(module_name, class_name, *args, **kwargs): class_meta = getattr(module_name, class_name) obj = class_meta(*args, **kwargs) return objdef 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