12. Biz业务层

前面的章节我们把大量的业务函数都放在了views.py里,按照目前这一的写法,当我们编写的系统复杂较高时,我们的views.py将会越来越复杂,大量的业务函数包含其中使其成为一个包罗万象的文件。本章我们将阐述增加一个业务逻辑层来解决view层的复杂度,相当于在model层和view层中增加一个业务逻辑业务层Biz层,接下来我们根据这个思路来重构我们前面章节的代码。

12.1. 增加inventoryBiz类文件

在inventory app添加一个新的inventoryBiz类文件,然后我们把views.里的updatingInventoryIn函数迁移到新的类文件里面来,代码如下:

from inventory.models import *

class InventoryBiz(object):
#注意这里类函数要增加一个Self参数
def updatingInventoryIn(self,inStockBill,inventory):
if (inventory.InventoryId == None):
inventory.Item = inStockBill.Item
inventory.Amount = 0 inventory.Amount = inventory.Amount + inStockBill.Amount

这里注意:我们手动增加的文件编码格式是ANSI的我们需要用notepad打开inventoryBiz.py文件另存为修改该文件的编码格式,否则中文注释会导致python运行环境报错。(updatingInventoryIn采用首字母小写的Camel命名模式,后面我们的方法名都调整为这一命名习惯)

我们调整一下测试代码来满足这次代码重构:

from inventory.InventoryBiz import

 InventoryBiz

...

    #如何构建更新库存的函数,让其可具备测试调用,

    #当前我们把函数放在views.py迁移到InventoryBiz.py里

     biz 

=

 InventoryBiz()

    biz.updatingInventoryIn(inStockBill,inventory)

    #校验测试是否满足当前场景

     self.assertEqual(inventory.Amount ,20)

    inventory = Inventory() #当前没有库存数据,我们创建对象属性都没有赋值

     biz.updatingInventoryIn(inStockBill,inventory)

    self.assertEqual(inventory.Amount ,10)

    self.assertEqual(inventory.Item.ItemId ,inStockBill.Item.ItemId)

Run单元测试,测试通过。这里我们就会发现单元测试对于代码重构来说是多么的重要,代码结构的改变如果影响到原来的测试业务,简单调整一下,然后通过回归测试很快就知道这次调整是否满足原来的设计要求,所以说单元测试对于系统的代码重构来说简直就是一个利器。

接下来,我们重构和调整一下AddInStockBill函数就完成了本次代码调整工作,代码如下:

@transaction.commit_on_success

def AddInStockBill(request):
if request.method == 'POST':
form = InStockBillForm(request.POST)
if form.is_valid():
cd = form.cleaned_data
inStockBill = InStockBill()
inStockBill.InStockBillCode = cd['InStockBillCode']
inStockBill.InStockDate = cd['InStockDate']
inStockBill.Amount = cd['Amount']
inStockBill.Operator = cd['Operator']
inStockBill.Item = cd['Item']
inventorys = inStockBill.Item.inventory_set.all()
if (inventorys.count()==0):
currentInventory = Inventory()
else:
currentInventory = inventorys[0] biz = InventoryBiz()
biz.updatingInventoryIn(inStockBill,currentInventory)
currentInventory.save() #更新库存
inStockBill.save() #保存入库单数据 return HttpResponseRedirect('/success/') else: form = InStockBillForm()
return render_to_response('InStockAdd.html',{'form': form}
,context_instance = RequestContext(request))

12.2. 继续重构inventoryBiz类

依据高内聚、低耦合的对象设计思路,我们继续重构代码,把AddInStockBill函数中获取当前物料的库存数据的代码迁移到inventoryBiz类中。

def getInventoryByItem(self,item) :
if (item != None):
inventorys = item.inventory_set.all()
if (inventorys.count()==0):
currentInventory = Inventory()
else:
currentInventory = inventorys[0] return currentInventory

最后我们的AddInStockBill变成了只负责把客户端传来的数据进行解码,然后委托调用相应biz业务层的方法,进行业务处理,最后把数据持久化到数据库中。自己不再包含业务逻辑代码,这种增加对象内聚的代码重构,便于以后我们快速定位bug和调整业务时修改代码,而不是在复杂的views.py文件中到处去找业务代码,这个也是面向对象编程的核心思想之一。

@transaction.commit_on_success
def AddInStockBill(request):
if request.method == 'POST':
form = InStockBillForm(request.POST)
if form.is_valid():
cd = form.cleaned_data
inStockBill = InStockBill()
inStockBill.InStockBillCode = cd['InStockBillCode']
inStockBill.InStockDate = cd['InStockDate']
inStockBill.Amount = cd['Amount']
inStockBill.Operator = cd['Operator']
inStockBill.Item = cd['Item'] biz = InventoryBiz()
#获得入库单物料库存
currentInventory = biz.getInventoryByItem(inStockBill.Item)
biz.updatingInventoryIn(inStockBill,currentInventory) #更新库存数量
currentInventory.save() #

提交更新后的库存数据

              inStockBill.save() 

#保存入库单数据

            return HttpResponseRedirect('/success/')
else:
form = InStockBillForm()
return render_to_response('InStockAdd.html',{'form': form}
,context_instance = RequestContext(request))

12.3. 代码的持续改进

根据敏捷开发提倡的“敏捷团队注重简易,这样做可以消除那些没必要的复杂,只需专注于开发当前所需要的功能和最简单的设计。如果能使用简单的方法来帮助一个敏捷团队马上开发出需要的软件,而不浪费人力和资源,这就是他们给那些投资的用户以最好和最直接利益的方法。”

前面的AddInStockBill是满足当前需要的,所以根据简易原则,当前在views.py里直接调用库存的保存方法是满足要求的。可是如果接下来我们的需求增加或者变更,如:需要在入库单Model数据提交前做model的合法性验证,那么我该如何来编写我们的代码呢?最简单的写法就是在AddInStockBill函数的inStockBill.save()语句前增加业务判断,在model合法后再调用该保存语句。

笔者注:这里的验证可以理解是服务端的验证,与django Form表单的前端验证分开来看,这样未来我们才能把服务端与页面端的绑定分离,从而支持一个服务接口可以供页面调用也可以供其它客户端调用,如:android等。

if not inStockBill.InStockBillCode:

    validMsg = validMsg + '入库单编号不能为空!'

if validMsg == '':
inStockBill.save()

当这样的需求出现时,为了提高系统的内聚性和解少views.py的复杂性,我们增加一个InStockBillBiz来专门负责关于InStockBill模型的相关功能代码。在InStockBillBiz里我们增加一个save函数用来处理InStockBill model的保存,代码如下:

from inventory.models import *
class InStockBillBiz(object):
"""description of class"""
def save(self,inStockBill):
try:
if not inStockBill.InStockBillCode:
validMsg = '入库单编号不能为空!'
if not inStockBill.InStockDate:
validMsg = validMsg + '入库单时间不能为空!'
if validMsg != '':
raise "ValidationException", validMsg
inStockBill.save()
except "ValidationException", arg:
raise "ValidationException", arg
except Exception, e:
raise Exception(e)

如果model复杂,需要做的数据校验比较多,代码进一步重构改进如下,编写一个专门处理数据校验的函数:

from inventory.models import *
class InStockBillBiz(object):
"""description of class"""
def save(self,inStockBill):
try: validMsg = self.validBeforeSave(inStockBill)
if validMsg != '':
raise "ValidationException", validMsg
inStockBill.save()
except "ValidationException", arg:
raise "ValidationException", arg
except Exception, e:
raise Exception(e) def validBeforeSave(self, inStockBill):
validMsg = ''
if not inStockBill:
validMsg = validMsg + '入库单编号不能为空!'
if not inStockBill.InStockDate:
validMsg = validMsg + '入库单时间不能为空!' return validMsg

代码这样重构的好处就是以后定位Bug以及增加数据校验的机制或者规则会方便很多,代码的可读性大大提高,这就是面向对象编程中高内聚的原则,单一对象尽量少的职责,只专注于自己的事情,而系统功能的形成更多的通过对象的组合模式来实现。对于函数来说也是如此,一个函数的功能尽快可能单一,处理的事情尽量少,后面通过委托调用来组合,满足功能需求。

敏捷编程的一个大前提就是我们的复杂的功能代码是通过渐进的模式来实现的,如果简单的模式能够满足需求,我们就采用简单的方式实现,当简单的代码编写不能满足需求了,我们就重构我们的代码,通过内聚的方法,增加新的类来专门负责某一类功能代码。

最后,我们的views. AddInStockBill就变成了如下这样:

@transaction.commit_on_success
def AddInStockBill(request):
if request.method == 'POST':
form = InStockBillForm(request.POST)
if form.is_valid():
cd = form.cleaned_data
inStockBill = InStockBill()
inStockBill.InStockBillCode = cd['InStockBillCode']
inStockBill.InStockDate = cd['InStockDate']
inStockBill.Amount = cd['Amount']
inStockBill.Operator = cd['Operator']
inStockBill.Item = cd['Item'] biz = InventoryBiz()
#获得入库单物料库存
currentInventory = biz.getInventoryByItem(inStockBill.Item)
biz.updatingInventoryIn(inStockBill,currentInventory)
currentInventory.save() #更新库存 billBiz = InStockBillBiz()
billBiz.save(inStockBill)#保存入库单数据 return HttpResponseRedirect('/success/')
else:
form = InStockBillForm()
return render_to_response('InStockAdd.html',{'form': form}
,context_instance = RequestContext(request))

12.4. 小结

业务逻辑层的增加让我们的项目完整的体现了视图、模型和控制器(业务逻辑层)三层架构模式,后面的章节我们会逐步说到这一代码结构变化带来的好处。Biz层代码只负责处理系统的业务逻辑,实现了类设计的高内聚”要求,同时,这样的代码结构也为我们编写业务逻辑单元测试提供了好的架构体系,单元测试主要检验业务逻辑是否满足系统设计要求。

下一章节我们将描述MVC架构如何快速的实现views层的替换,或者说系统同时支持不同的视图层。

Python开发入门与实战12-业务逻辑层的更多相关文章

  1. Python开发入门与实战13-基于模板的界面

    13. 基于模板的界面 本章我们将继续基于库存的简单例子来阐述如何在python django中体现MVC的架构,根据djangobook说明: M:数据存取部分,由django数据库层处理,本章要讲 ...

  2. Python开发入门与实战14-基于Extjs的界面

    14. 基于Extjs的界面 上一章我们实现了一个原生的html例子,本章我们将采用Extjs实现界面的展现,来说明MVC模式下我们是怎么考虑界面与业务层的关系的. 14.1. 引用Extjs目录 首 ...

  3. Python开发入门与实战22-简单消息回复

    22. 简单消息回复 本章节我们来实现一个微信库存查询功能,使用我们前面的BIZ业务逻辑层示例如何利用微信入口来实现文本消息类的库存查询服务. 22.1. 在responseMsg函数里增加处理微信文 ...

  4. Python开发入门与实战10-事务

    1. 事务 本章我们将通过一个例子来简要的说明“事务”,这个开发实战里经常遇到的名词.事务是如何体现在一个具体的业务和系统的实现里. 事务是通过将一组相关操作组合为一个,要么全部成功要么全部失败的单元 ...

  5. Python开发入门与实战5-django模型

    5.Django模型 在当今的Web 应用中,主观逻辑经常牵涉到与数据库的交互,数据库驱动网站.在后台连接数据库服务器,从中取出一些数据,然后在 Web 页面用各种各样的格式展示这些数据.这个网站也可 ...

  6. Python开发入门与实战11-单元测试

    11. 单元测试 本章节我们来讲讲django工程中如何实现单元测试,单元测试如何编写以及在可持续项目中单元测试的重要性. 下面是单元测试的定义: 单元测试是开发者编写的一小段代码,用于检验被测代码的 ...

  7. Python开发入门与实战1-开发环境

    1.搭建Python Django开发环境 1.1.Python运行环境安装 Python官网:http://www.python.org/ Python最新源码,二进制文档,新闻资讯等可以在Pyth ...

  8. Python开发入门与实战8-基于Java的集成开发环境

    8. 基于Java的Python的集成开发环境 目前为止我们所有的代码和例子都是通过Notepad文本编辑器来实现的,实际项目开发中这种编码模式效率较低(大虾除外),使用IDE集成开发环境常常大幅度的 ...

  9. Python开发入门与实战15-IIS部署

    15. IIS部署 前面的章节我们完成了基本的业务功能的开发,本章节我们来说说python django项目如何部署到实际的运行环境,完成开发系统的发布工作. Python Django 项目部署发布 ...

随机推荐

  1. 给NIOS II CPU增加看门狗定时器并使用

    给NIOS II CPU增加看门狗定时器并使用   配置看门狗定时器: 设置计时溢出时间为1秒 计数器位宽为32位 勾选No Start/Stop control bits 勾选Fixed perio ...

  2. 33、mybatis(二)

    第十六章回顾SQL99中的连接查询 1)内连接 2)外连接 3)自连接 第十七章回顾hibernate多表开发 1)一对一 2)一对多 3)多对多 第十八章 mybatis一对一映射[学生与身份证] ...

  3. Apache配置多个网站

    你可以全用本地私有ip地址创建多个站点 127.0.0.x,这个网段的所有ip都是指向本机的,并且可以区分,这是计算机的私有ip地址,供测试用的,配置方法如下 一.打开httpd.conf 1.从Se ...

  4. A feature in Netsuite Reports > Financial > Balance Sheet

    最新版本的Customize balance sheet page Left side > Layout > Add Reference Row Then in right side, y ...

  5. (转)MySQL联表查询

    资料源于网络   一.内联结.外联结.左联结.右联结的含义及区别在SQL标准中规划的(Join)联结大致分为下面四种:1.内联结:将两个表中存在联结关系的字段符合联结关系的那些记录形成记录集的联结.2 ...

  6. Working with Binary Data in Python

    http://www.devdungeon.com/content/working-binary-data-python

  7. SQL Prompt自定义代码片段

    新增代码片段: 代码片段管理: 代码片段中可以使用以下占位符:详见参考: $DATE$ 插入当前日期. $TIME$ 插入当前时间 $USER$ 插入当前电脑的用户名 $PASTE$ 插入剪切板内容 ...

  8. php黑魔法之解析问题

    code如下: <?php $a=$_GET[a]; $b=is_file($a); var_dump($b); ?> 可以看到1.ph< 返回了true 这样不就可以绕过这个的过滤 ...

  9. IE的条件注释

    <!--[if !IE]><!--> 除IE外都可识别 <!--<![endif]--> <!--[if IE]> 所有的IE可识别 <![ ...

  10. EasyUi 分页 和 表格数据加载

    这里说明的是将说有数据先返回到前端再由前端去分页,性能可能没有先在后台分好页再返回给前端高 但如果操作不涉及大数据的话也没什么大问题,具体问题具体分析 要使用分页控件首先要声明初始化一下: //设置分 ...