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