聊聊那些專為演算法設計的模式——訪問模式
AI越來越火熱,人工智慧已然成風!而人工智慧最重要是各種演算法,因此機器學習越來越受到追捧,演算法越來越被重視。
作為一個演算法的研究者,寫出一手高階演算法當然是令人興奮的一件事!但你是否有時會有這種感覺: 1. 寫的演算法很難通用於所有的資料型別!每來一個新型別的資料,又得改一下演算法,或新加一個方法來支援這種型別。 2. 有時候多個演算法需要靈活組合,甚至每個演算法的順序不一樣都會產生不一樣的效果;每一種組合都要為其構建一個新演算法,即累又麻煩。 3. 演算法越來越多,自建的演算法庫也越來越龐大而難於管理;
這個時候,讓你的演算法具有更好通用性、拓展性就顯得極為重要!因此,你必須要掌握幾個重要的設計模式來優化你的程式碼,解決這些問題。今天就來聊聊那些專為演算法設計的模式:策略模式、模板方法模式、訪問模式。
訪問模式
封裝一些作用於某種資料結構中各元素的操作,它可以在不改變資料結構的前提下定義作用於這些元素的新的操作。
訪問模式的核心思想在於:可以在不改變資料結構的前提下定義作用於這些元素的新操作。將資料結構和具體演算法進行解耦,而且能更方便地拓展新的操作。
訪問模式的程式碼框架
from abc import ABCMeta, abstractmethod
# 引入ABCMeta和abstractmethod來定義抽象類和抽象方法
class DataNode(metaclass=ABCMeta):
"資料結構類"
def accept(self, visitor) :
"接受訪問者的訪問"
visitor.visit(self)
class Visitor(metaclass=ABCMeta):
"訪問者"
@abstractmethod
def visit(self, data):
"對資料物件的訪問操作"
pass
class ObjectStructure:
"資料結構的管理類,也是資料物件的一個容器,可遍歷容器內的所有元素"
def __init__(self):
self.__datas = []
def add (self, dataElement):
self.__datas.append(dataElement)
def action(self, visitor):
"進行資料訪問的操作"
for data in self.__datas:
visitor.visit(data)
這裡Visitor的訪問方法只有一個visit(),是因為Python不支援方法的過載。在一些強型別的語言(如Java、C++)中,應該有多個方法,針對每一個DataNode子類定義一個過載方法。
訪問模式的類圖結構:
DataNode是資料結點,可接受(accept)訪問者的訪問,DataNodeA和DataNodeB是它的具體實現類。Visitor是訪問者類,可訪問(visit)具體的物件。ObjectStructure是資料結構的管理類,也是資料物件的一個容器,可遍歷容器內的所有元素。
應用案例
在寵物界中,貓和狗歷來就是一對歡喜冤家!假設寵物店中有N只貓和M只狗。我們要進行下面這3個操作: 1. 在這些寵物中雌貓、雄貓、雌狗、雄狗的數量分別是多少。 2. 貓的平均體重和狗的平均體重分別是多少。
這個時候,如果要在貓和狗的物件上新增這些操作,將會增加非常多的方法而汙染原有的物件;而且這些操作的拓展性也將非常差。這時訪問模式是解決這個問題的最好方法,我們一起看一下具體的實現如下:
原始碼示例:
class Animal(DataNode):
"""動物類"""
def __init__(self, isMale, weight):
self.__isMale = isMale
self.__weight = weight
def isMale(self):
return self.__isMale
def getWeight(self):
return self.__weight
class Cat(Animal):
"""貓"""
def speak(self):
print("miao~")
class Dog(Animal):
"""狗"""
def speak(self):
print("wang~")
class GenderCounter(Visitor):
"""性別統計"""
def __init__(self):
self.__maleCat = 0
self.__femaleCat = 0
self.__maleDog = 0
self.__femalDog = 0
def visit(self, data):
if isinstance(data, Cat):
if data.isMale():
self.__maleCat += 1
else:
self.__femaleCat += 1
elif isinstance(data, Dog):
if data.isMale():
self.__maleDog += 1
else:
self.__femalDog += 1
else:
print("Not support this type")
def getInfo(self):
print(str(self.__maleCat) + "只雄貓," + str(self.__femaleCat) + "只雌貓,"
+ str(self.__maleDog) + "只雄狗," + str(self.__femalDog) + "只雌狗。")
class WeightCounter(Visitor):
"""體重的統計"""
def __init__(self):
self.__catNum = 0
self.__catWeight = 0
self.__dogNum = 0
self.__dogWeight = 0
def visit(self, data):
if isinstance(data, Cat):
self.__catNum +=1
self.__catWeight += data.getWeight()
elif isinstance(data, Dog):
self.__dogNum += 1
self.__dogWeight += data.getWeight()
else:
print("Not support this type")
def getInfo(self):
print("貓的平均體重是:%0.2fkg, 狗的平均體重是:%0.2fkg" %
((self.__catWeight / self.__catNum),(self.__dogWeight / self.__dogNum)))
測試程式碼:
def testAnimal():
animals = ObjectStructure()
animals.add(Cat(True, 5.1))
animals.add(Cat(False, 4.3))
animals.add(Dog(True, 8))
animals.add(Dog(False, 21))
animals.add(Dog(False, 25))
genderCounter = GenderCounter()
animals.action(genderCounter)
genderCounter.getInfo()
print()
weightCounter = WeightCounter()
animals.action(weightCounter)
weightCounter.getInfo()
print()
輸出結果:
1只雄貓,1只雌貓,1只雄狗,2只雌狗。
貓的平均體重是:4.70kg, 狗的平均體重是:18.00kg
訪問模式將“資料”和“操作演算法”分離,降低了耦合度。將相關元素物件的訪問行為集中到一個訪問者物件中,而不是分散在一個個的元素類中,類的職責更加清晰;操作演算法更易拓展。訪問模式特別適合應用於以下場景:物件結構中包含的物件型別比較少,而且這些類需要比較固定,很少改變,但經常需要在此物件結構上定義新的操作。