一、藥房業(yè)務(wù)系統(tǒng)
假設(shè)一個(gè)藥房,有一些大夫,一個(gè)藥品劃價(jià)員和一個(gè)藥房管理員,它們通過一個(gè)藥房管理系統(tǒng)組織工作流程。大夫開出藥方后,藥品劃價(jià)員確定藥品是否正常,價(jià)格是否正確;通過后藥房管理員進(jìn)行開藥處理。該系統(tǒng)可以如何實(shí)現(xiàn)?最簡單的想法,是分別用一個(gè)一個(gè)if…else…把劃價(jià)員處理流程和藥房管理流程實(shí)現(xiàn),這樣做的問題在于,擴(kuò)展性不強(qiáng),而且單一性不強(qiáng),一旦有新藥的加入或者劃價(jià)流程、開藥流程有些變動(dòng),會(huì)牽扯比較多的改動(dòng)。今天介紹一種解決這類問題的模式:訪問者模式。
首先,構(gòu)造藥品類和工作人員類:
class Medicine:
name=""
price=0.0
def __init__(self,name,price):
self.name=name
self.price=price
def getName(self):
return self.name
def setName(self,name):
self.name=name
def getPrice(self):
return self.price
def setPrice(self,price):
self.price=price
def accept(self,visitor):
pass
class Antibiotic(Medicine):
def accept(self,visitor):
visitor.visit(self)
class Coldrex(Medicine):
def accept(self,visitor):
visitor.visit(self)
藥品類中有兩個(gè)子類,抗生素和感冒藥;
class Visitor:
name=""
def setName(self,name):
self.name=name
def visit(self,medicine):
pass
class Charger(Visitor):
def visit(self,medicine):
print "CHARGE: %s lists the Medicine %s. Price:%s " % (self.name,medicine.getName(),medicine.getPrice())
class Pharmacy(Visitor):
def visit(self,medicine):
print "PHARMACY:%s offers the Medicine %s. Price:%s" % (self.name,medicine.getName(),medicine.getPrice())
工作人員分為劃價(jià)員和藥房管理員。
在藥品類中,有一個(gè)accept方法,其參數(shù)是個(gè)visitor;而工作人員就是從Visitor類中繼承而來的,也就是說,他們就是Visitor,都包含一個(gè)visit方法,其參數(shù)又恰是medicine。藥品作為處理元素,依次允許(Accept)Visitor對(duì)其進(jìn)行操作,這就好比是一條流水線上的一個(gè)個(gè)工人,對(duì)產(chǎn)品進(jìn)行一次次的加工。整個(gè)業(yè)務(wù)流程還差一步,即藥方類的構(gòu)建(流水線大機(jī)器)。
class ObjectStructure:
pass
class Prescription(ObjectStructure):
medicines=[]
def addMedicine(self,medicine):
self.medicines.append(medicine)
def rmvMedicine(self,medicine):
self.medicines.append(medicine)
def visit(self,visitor):
for medc in self.medicines:
medc.accept(visitor)
藥方類將待處理藥品進(jìn)行整理,并組織Visitor依次處理。
業(yè)務(wù)代碼如下:
if __name__=="__main__":
yinqiao_pill=Coldrex("Yinqiao Pill",2.0)
penicillin=Antibiotic("Penicillin",3.0)
doctor_prsrp=Prescription()
doctor_prsrp.addMedicine(yinqiao_pill)
doctor_prsrp.addMedicine(penicillin)
charger=Charger()
charger.setName("Doctor Strange")
pharmacy=Pharmacy()
pharmacy.setName("Doctor Wei")
doctor_prsrp.visit(charger)
doctor_prsrp.visit(pharmacy)
打印如下:
CHARGE: Doctor Strange lists the Medicine Yinqiao Pill. Price:2.0
CHARGE: Doctor Strange lists the Medicine Penicillin. Price:3.0
PHARMACY:Doctor Wei offers the Medicine Yinqiao Pill. Price:2.0
PHARMACY:Doctor Wei offers the Medicine Penicillin. Price:3.0
二、訪問者模式
訪問者模式的定義如下:封裝一些作用于某種數(shù)據(jù)結(jié)構(gòu)中的各元素的操作,它可以在不改變數(shù)據(jù)結(jié)構(gòu)的前提下定義于作用于這些元素的新操作。
提到訪問者模式,就不得不提一下雙分派。分派分為靜態(tài)分派和動(dòng)態(tài)分派。首先解釋下靜態(tài)分派,靜態(tài)分派即根據(jù)請(qǐng)求者的名稱和接收到的參數(shù),決定多態(tài)時(shí)處理的操作。比如在Java或者C++中,定義名稱相同但參數(shù)不同的函數(shù)時(shí),會(huì)根據(jù)最終輸入的參數(shù)來決定調(diào)用哪個(gè)函數(shù)。雙分派顧名思義,即最終的操作決定于兩個(gè)接收者的類型,在本例中,藥品和工作人員互相調(diào)用了對(duì)方(藥品的accept和工作人員的visit中,對(duì)方都是參數(shù)),就是雙分派的一種應(yīng)用。
那么Python支持靜態(tài)分派么?先看下面的一個(gè)例子。
def max_num(x,y,z):
return max(max(x,y),z)
def max_num(x,y):
return max(x,y)
if __name__=="__main__":
print max_num(1,2,4)
打印如下:
Traceback (most recent call last):
File "D:/WorkSpace/Project/PyDesignMode/example.py", line 786, in
TypeError: max_num() takes exactly 2 arguments (3 given)
可見,Python原生是不支持靜態(tài)分派的,因而也不直接支持更高層次的分派。訪問者模式實(shí)現(xiàn)的分派,是一種動(dòng)態(tài)雙分派。但這并不妨礙Python通過訪問者模式實(shí)現(xiàn)一種基于類的“雙分派效果”。Python多分派可以參考David Mertz 博士的一篇文章:可愛的Python:多分派—用多元法泛化多樣性。
三、訪問者模式的優(yōu)點(diǎn)和應(yīng)用場景
優(yōu)點(diǎn):
1、將不同的職責(zé)非常明確地分離開來,符合單一職責(zé)原則;
2、職責(zé)的分開也直接導(dǎo)致擴(kuò)展非常優(yōu)良,靈活性非常高,加減元素和訪問者都非常容易。
應(yīng)用場景:
1、要遍歷不同的對(duì)象,根據(jù)對(duì)象進(jìn)行不同的操作的場景;或者一個(gè)對(duì)象被多個(gè)不同對(duì)象順次處理的情況,可以考慮使用訪問者模式。除本例外,報(bào)表生成器也可以使用訪問者模式實(shí)現(xiàn),報(bào)表的數(shù)據(jù)源由多個(gè)不同的對(duì)象提供,每個(gè)對(duì)象都是Visitor,報(bào)表這個(gè)Element順次Accept各訪問者完善并生成對(duì)象。
四、訪問者模式的缺點(diǎn)
1、訪問者得知了元素細(xì)節(jié),與最小隔離原則相悖;
2、元素變更依舊可能引起Visitor的修改。