Pytest系列()- 超美測試報告外掛之allure-pytest的基礎使用
概述
訪問者模式是一種較為複雜的行為型設計模式,它包含訪問者和被訪問元素兩個主要組成部分,這些被訪問的元素通常具有不同的型別,且不同的訪問者可以對它們進行不同的訪問操作。例如處方單中的各種藥品資訊就是被訪問的元素,而劃價人員和藥房工作人員就是訪問者。訪問者模式使得使用者可以在不修改現有系統的情況下擴充套件系統的功能,為這些不同型別的元素增加新的操作。
在使用訪問者模式時,被訪問元素通常不是單獨存在的,它們儲存在一個集合中,這個集合稱為 "物件結構",訪問者通過遍歷物件結構實現對其中儲存的元素的逐個操作。
訪問者模式定義如下:提供一個作用於某物件結構中的各元素的操作表示,它使得可以在不改變各元素的類的前提下定義作用於這些元素的新操作
從圖中可以看出,在訪問者模式結構圖中包含以下 5 個角色:
- Vistor(抽象訪問者):抽象訪問者為物件結構中每一個具體元素類 ConcreteElement 宣告一個訪問操作,從這個操作的名稱或引數型別可以清楚知道需要訪問的具體元素的型別,具體訪問者需要實現這些操作方法,定義對這些元素的訪問操作。
- Concrete Visitor(具體訪問者):具體訪問者實現了每個由抽象訪問者宣告的操作,每一個操作用於訪問物件結構中一種型別的元素。
- Element(抽象元素):抽象元素一般是抽象類或者介面,它定義一個 accept() 方法,該方法通常以一個抽象訪問者作為引數。
- ConcreteElement(具體元素):具體元素實現了 accept() 方法,在 accept() 方法中用訪問者的訪問方法以便完成對一個元素的操作。
- ObjectStructure(物件結構):物件結構是一個元素的集合,它用於存放元素物件,並且提供了遍歷其內部元素的方法。它可以結合組合模式來實現,也可以是一個簡單的集合物件,例如一個List物件或一個Set物件。
訪問者模式中物件結構儲存了不同型別的元素物件,以供不同訪問者訪問。訪問者模式包括兩個層次結構:一個是訪問者層次結構,提供了抽象訪問者和具體訪問者;另一個是元素層次結構,提供了抽象元素和具體元素。相同的訪問者可以以不同的方式訪問不同的元素,相同的元素可以接受不同訪問者以不同訪問方式訪問。在訪問者模式中,增加新的訪問者無須修改原有系統,系統具有較好的可擴充套件性。
在訪問者模式中,抽象訪問者定義了訪問元素物件的方法,通常為每一種型別的元素物件都提供一個訪問方法,而具體訪問者可以實現這些訪問方法。這些訪問方法的命名一般有兩種方式:一種是直接在方法名中標明待訪問元素物件的具體型別,例如visitElementA(ElementA elementA);
另有一種是統一取名為 visit()
,通過引數型別的不同來定義一系列過載的visit() 方法。當然,如果所有的訪問者對某一型別的元素的訪問操作都相同,則可以將操作程式碼移到抽象訪問者類中,其典型程式碼如下:
class Visitor {
public:
virtual void visit(ConcreteElementA* elementA) = 0;
virtual void visit(ConcreteElementB* elementB) = 0;
void visit(ConcreteElementC* elementC) {
// 元素 ConcreteElementC 的操作程式碼
}
};
在這裡使用了過載 visit() 方法的方式來定義多個方法用於操作不同型別的元素物件。在抽象訪問者 Visitor 類的子類 ConcreteVisitor 中實現了抽象的訪問方法,用於定義對不同型別元素物件的操作,具體訪問者類典型程式碼如下:
class ConcreteVisitor : public Visitor {
public:
virtual void visit(ConcreteElementA* elementA) override;
virtual void visit(ConcreteElementB* elementB) override;
};
對於元素類而言,在其中一般都定義了一個 accept() 方法,用於接收訪問者的訪問,典型的抽象元素類程式碼如下:
class Element {
public:
virtual void accept(Visitor* visitor) = 0;
};
需要注意的是,該方法傳入了一個抽象訪問者 Visitor 型別的引數,即針對抽象訪問者進行程式設計,而不是具體訪問者,在程式執行時再確定具體訪問者的型別,並呼叫具體訪問者物件的 visit() 方法實現對元素物件的操作。在抽象元素類 Element 的子類中實現了 accept() 方法,用於接受訪問者的訪問,在具體元素類中還可以定義不同型別的元素所特有的業務方法,其典型程式碼如下:
class ConcreteElementA : public Element {
public:
virtual void accept(Visitor* visitor) {
visitor->visit(this);
}
void operationA() {
// 業務方法
}
};
在具體元素類 ConcreteElementA 的 accept() 方法中,通過呼叫 Visitor 類的 visit() 方法實現對元素的訪問,並以當前物件作為 visit() 方法的引數。其具體執行過程如下:
- 呼叫具體元素類的 accept(Visitor* visitor) 方法,並將 Visitor 子類物件作為其引數。
- 在具體元素類 accept(Visitor* visitor) 方法內部呼叫傳入的 Visitor 物件的 visit() 方法,例如 visit(ConcreteElementA* elementA),將當前具體元素類物件(this)作為引數,例如 visitor.visit(this)。
- 執行 Visitor 物件的 visit() 方法,在其中還可以呼叫具體元素物件的業務方法。
這種呼叫機制也稱為 "雙重分派"。正因為使用了雙重分派機制,使得增加新的訪問者無須修改現有類庫程式碼,只需將新的訪問者物件作為引數傳入具體元素物件的 accept() 方法,程式執行時將回調在新增 Visitor 類中定義的 visit() 方法,從而增加新的元素訪問方式。
在訪問者模式中,物件結構是一個集合,它用於儲存元素物件並接受訪問者的訪問,其典型程式碼如下:
class ObjectStructure {
public:
// 接受訪問者的訪問操作
void accept(Visitor* visitor) {
for (int i = 0; i < vec.size(); i++) {
vec[i]->accept(visiotr);
}
}
void addElement(Element* element) {
vec.push_back(element);
}
void removeElement(Element* element) {
for (auto iter = vec.begin(); iter != vec.end(); ++iter) {
if (*iter == element) {
iter = vec.erase(iter);
}
}
}
private:
vector<Element*> vec;
};
訪問者模式和組合模式聯用
在訪問者模式中,包含一個用於儲存元素物件集合的物件結構,通常可以使用迭代器來遍歷物件結構,同時具體元素之間可以存在整體與部分關係,有些元素作為容器物件,有些元素作為成員物件,可以使用組合模式來組織元素。引入組合模式後的訪問者模式結構圖如圖所示:
需要注意的是,在上圖所示結構中,由於葉子元素的遍歷操作已經在容器元素中完成,因此要防止單獨將已增加到容器元素中的葉子元素再次加入物件結構中,物件結構中只儲存容器元素和孤立的葉子元素。
訪問者模式總結
由於訪問者模式的使用條件較為苛刻,本身結構也較為複雜,因此在實際應用中使用頻率不是特別高。當系統中存在一個較為複雜的物件結構,且不同訪問者對其所採取的操作也不相同時,可以考慮使用訪問者模式進行設計。在XML文件解析、編譯器的設計、複雜集合物件的處理等領域,訪問者模式得到了一定的應用。
優點
- 增加新的訪問操作很方便。使用訪問者模式,增加新的訪問操作就意味著增加個新的具體訪問者類,實現簡單,無須修改原始碼,符合開閉原則。
- 將有關元素物件的訪問行為集中到一個訪問者物件中,而不是分散在一個個的元素類中。類的職責更加清晰,有利於物件結構中元素物件的複用,相同的物件結構可以供多個不同的訪問者訪問。
- 讓使用者能夠在不修改現有元素類層次結構的情況下,定義作用於該層次結構的操作。
缺點
- 增加新的元素類很困難。在訪問者模式中,每增加一個新的元素類都意味著要在抽象訪問者角色中增加一個新的抽象操作,並在每一個具體訪問者類中增加相應的具體操作,這違背了開閉原則的要求。
- 破壞封裝。訪問者模式要求訪問者物件訪問並呼叫每一個元素物件的操作,這意味著元素物件有時候必須暴露一些自己的內部操作和內部狀態,否則無法供訪問者訪問。
適用場景
- 一個物件結構包含多個型別的物件,希望對這些物件實施一些依賴其具體型別的操作。在訪問者中針對每一種具體的型別都提供了一個訪問操作,不同型別的物件可以有不同的訪問操作。
- 需要對一個物件結構中的物件進行很多不同的並且不相關的操作,而且需要避免讓這些操作“汙染”這些物件的類,也不希望在增加新操作時修改這些類。訪問者模式將相關的訪問操作集中起來定義在訪問者類中,物件結構可以被多個不同的訪問者類所使用,將物件本身與物件的訪問操作分離。
- 物件結構中物件對應的類很少改變,但經常需要在此物件結構上定義新的操作。