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——抽象工厂模式的更多相关文章

  1. [Python设计模式] 第7章 找人帮忙追美眉——代理模式

    github地址:https://github.com/cheesezh/python_design_patterns 题目1 Boy追求Girl,给Girl送鲜花,送巧克力,送洋娃娃. class ...

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

    github地址:https://github.com/cheesezh/python_design_patterns 题目 设计一个简历类,必须有姓名,可以设置性别和年龄,即个人信息,可以设置曾就职 ...

  3. [Python编程实战] 第一章 python的创建型设计模式1.1抽象工厂模式

    注:关乎对象的创建方式的设计模式就是“创建型设计模式”(creational design pattern) 1.1 抽象工厂模式 “抽象工厂模式”(Abstract Factory Pattern) ...

  4. C#设计模式学习笔记:(3)抽象工厂模式

    本笔记摘抄自:https://www.cnblogs.com/PatrickLiu/p/7596897.html,记录一下学习过程以备后续查用. 一.引言 接上一篇C#设计模式学习笔记:简单工厂模式( ...

  5. 《Think Python》第15章学习笔记

    目录 <Think Python>第15章学习笔记 15.1 程序员定义的类型(Programmer-defined types) 15.2 属性(Attributes) 15.3 矩形( ...

  6. 大话设计模式C++实现-第15章-抽象工厂模式

    一.UML图 二.概念 抽象方法模式(Abstract Factory):提供一个创建一系列相关或互相依赖对象的接口,而无需指定他们详细的类. 三.包括的角色 (1)抽象工厂 (2)详细工厂:包含详细 ...

  7. 设计模式(Python)-简单工厂,工厂方法和抽象工厂模式

    本系列文章是希望将软件项目中最常见的设计模式用通俗易懂的语言来讲解清楚,并通过Python来实现,每个设计模式都是围绕如下三个问题: 为什么?即为什么要使用这个设计模式,在使用这个模式之前存在什么样的 ...

  8. 设计模式之第2章-抽象工厂模式(Java实现)

    设计模式之第2章-抽象工厂模式(Java实现) “上次是我的不对,贿赂作者让我先讲来着,不过老婆大人大人有大量,不与我计较,这次还让我先把上次未讲完的应用场景部分给补充上去,有妻如此,夫复何求.”(说 ...

  9. 大话设计模式Python实现- 抽象工厂模式

    抽象工厂模式(Abstract Factory Pattern):提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们的类 下面是一个抽象工厂的demo: #!/usr/bin/env pyth ...

随机推荐

  1. BZOJ3230 相似子串 字符串 SA ST表

    原文链接http://www.cnblogs.com/zhouzhendong/p/9033092.html 题目传送门 - BZOJ3230 题意 给定字符串$s$.长度为$n$. 现在有$Q$组询 ...

  2. Practice| 数组

    /* 从键盘确定班级的组号,在从键盘输入每一组的人数,并输入每一个学员的成绩,并求出,每一组的平均分, 全部的平均分,每一组的最高分,全部的最高分,并显示结果. */ class Test3{ pub ...

  3. ELM:ELM实现鸢尾花种类测试集预测识别正确率(better)结果对比—Jason niu

    load iris_data.mat P_train = []; T_train = []; P_test = []; T_test = []; for i = 1:3 temp_input = fe ...

  4. The path is not a valid path to the xxx kernel header

    在安装vmtools时无意中出现了这样的问题 1.gcc错误 Searching for GCC- The path "" is not valid path to the gcc ...

  5. Shell学习之结合正则表达式与通配符的使用(五)

    Shell学习之结合正则表达式与通配符的使用 目录 通配符 正则表达式与通配符 通配符 通配符的使用 正则表达式 正则表达式 正则表达式的使用 通配符 正则表达式与通配符 正则表达式用来在文件中匹配符 ...

  6. shell 运算符

    shell的逻辑运算符 涉及有以下几种类型,因此只要适当选择,可以解决我们很多复杂的判断,达到事半功倍效果. 一.逻辑运算符 逻辑卷标 表示意思 1. 关于档案与目录的侦测逻辑卷标! -f 常用!侦测 ...

  7. getting data from the keybroad

    public static String getString() throws IOException{ InputStreamReader isr = new InoutStreamReader(S ...

  8. SQL 自定义四舍五入

    --============================================== -- 自定义的四舍五入(四舍五入后的所有尾数遇进则进) -- by 小天使 2015-11-12 -- ...

  9. Redis自学笔记:3.5入门-集合类型

    3.5集合类型 3.5.1介绍 在集合中的每个元素都是不同的,且没有顺序 表3-4集合类型和列表类型的对比 - 集合类型 列表类型 存储内容 至多232-1个字符串 至多232-1个字符串 有序性 否 ...

  10. MongoError: Cannot update '__v' and '__v' at the same time,错误解决办法

    1.讲查询的结果,原封不动的插入到另一个表中,结果报错了:MongoError: Cannot update '__v' and '__v' at the same time,起初认为是mongodb ...