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命名模式,后面我们的方法名都调整为这一命名习惯)
我们调整一下测试代码来满足这次代码重构:
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层的替换,或者说系统同时支持不同的视图层。