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層的替換,或者說系統同時支持不同的視圖層。
