C++設計模式之訪問者模式(visitor)(行爲型)

一定義

1 概述

在軟件開發過程中,對於系統中的某些對象,它們存儲在同一個集合collection中,且具有不同的類型,而且對於該集合中的對象,可以接受一類稱爲訪問者的對象來訪問,而且不同的訪問者其訪問方式有所不同。
例子1:顧客在超市中將選擇的商品,如蘋果、圖書等放在購物車中,然後到收銀員處付款。在購物過程中,顧客需要對這些商品進行訪問,以便確認這些商品的質量,之後收銀員計算價格時也需要訪問購物車內顧客所選擇的商品。
此時,購物車作爲一個ObjectStructure(對象結構)用於存儲各種類型的商品,而顧客和收銀員作爲訪問這些商品的訪問者,他們需要對商品進行檢查和計價。不同類型的商品其訪問形式也可能不同,如蘋果需要過秤之後再計價,而圖書不需要。

2 定義

訪問者模式:表示一個作用於某對象結構中的各元素的操作。它使你可以在不改變各元素的類的前提下定義作用於這些元素的新操作。
1)訪問者模式中對象結構存儲了不同類型的元素對象,以供不同訪問者訪問。
2)訪問者模式包括兩個層次結構,一個是訪問者層次結構,提供了抽象訪問者和具體訪問者,一個是元素層次結構,提供了抽象元素和具體元素。

二ULM圖

角色

抽象訪問者(Vistor): 爲該對象結構中ConcreteElement的每一個類聲明一個Visit操作。該操作的名字和特徵標識了發送Visit請求給該訪問者的那個類。這使得訪問者可以確定正被訪問元素的具體的類。這樣訪問者就可以通過該元素的特定接口直接訪問它。
具體訪問者(ConcreteVisitor): — 實現每個由Visitor聲明的操作。每個操作實現本算法的一部分,而該算法片斷乃是對應於結構中對象的類。ConcreteVisitor爲該算法提供了上下文並存儲它的局部狀態。這一狀態常常在遍歷該結構的過程中累積結果。
 抽象元素(Element):定義一個Accept操作,它以一個訪問者爲參數。
具體元素(ConcreteElement):   實現Accept操作,該操作以一個訪問者爲參數。
對象結構(ObjectStructure): 能枚舉它的元素。可以提供一個高層的接口以允許該訪問者訪問它的元素。可以是一個複合或是一個集合,如一個列表或一個無序集合。

訪問者模式適用於數據結構相對穩定的系統,它把數據結構和作用於數據結構上的操作之間的耦合解脫開,使得操作集合可以相對自由地演化。

訪問者模式的目的是要把處理從數據結構分離出來。很多系統可以按照算法和數據機構分開,如果這樣的系統有比較穩定的數據結構,又有易於變化的算法的話,使用訪問者模式就是比較合適的,因爲訪問者模式使得算法操作的增加變得容易。反之,如果這樣的系統的數據結構對象易於變化,經常要有新的數據對象增加進來,就不適合使用訪問者模式。

優點

  • 使得增加新的訪問操作變得很容易。如果一些操作依賴於一個複雜的結構對象的話,那麼一般而言,增加新的操作會很複雜。而使用訪問者模式,增加新的操作就意味着增加一個新的訪問者類,因此,變得很容易。
  • 將有關元素對象的訪問行爲集中到一個訪問者對象中,而不是分散到一個個的元素類中。
  • 訪問者模式可以跨過幾個類的等級結構訪問屬於不同的等級結構的成員類。迭代子只能訪問屬於同一個類型等級結構的成員對象,而不能訪問屬於不同等級結構的對象。訪問者模式可以做到這一點。
  • 讓用戶能夠在不修改現有類層次結構的情況下,定義該類層次結構的操作。

缺點

  • 增加新的元素類很困難。在訪問者模式中,每增加一個新的元素類都意味着要在抽象訪問者角色中增加一個新的抽象操作,並在每一個具體訪問者類中增加相應的具體操作,違背了“開閉原則”的要求。
  • 破壞封裝。訪問者模式要求訪問者對象訪問並調用每一個元素對象的操作,這意味着元素對象有時候必須暴露一些自己的內部操作和內部狀態,否則無法供訪問者訪問。

適用性

在下列情況下使用Vi s i t o r模式:

  • 一個對象結構包含很多類對象,它們有不同的接口,而你想對這些對象實施一些依賴於其具體類的操作。
  • 需要對一個對象結構中的對象進行很多不同的並且不相關的操作,而你想避免讓這些操作“污染”這些對象的類。 Visitor使得你可以將相關的操作集中起來定義在一個類中。當該對象結構被很多應用共享時,用Visitor模式讓每個應用僅包含需要用到的操作。
  • 定義對象結構的類很少改變,但經常需要在此結構上定義新的操作。改變對象結構類需要重定義對所有訪問者的接口,這可能需要很大的代價。如果對象結構類經常改變,那麼可能還是在這些類中定義這些操作較好。

三 實例

購物車

•顧客在超市中將選擇的商品,如蘋果、圖書等放在購物車中,然後到收銀員處付款。在購物過程中,顧客需要對這些商品進行訪問,以便確認這些商品的質量,之後收銀員計算價格時也需要訪問購物車內顧客所選擇的商品。此時,購物車作爲一個ObjectStructure(對象結構)用於存儲各種類型的商品,而顧客和收銀員作爲訪問這些商品的訪問者,他們需要對商品進行檢查和計價。不同類型的商品其訪問形式也可能不同,如蘋果需要過秤之後再計價,而圖書不需要。使用訪問者模式來設計該購物過程。

ULM圖

實現代碼:

#include <iostream>
#include <string>
#include <list>

class Apple;
class Book;

//abstract Visitor
class Visitor
{
public:
	void setName(std::string name)
	{
		this->name = name;
	}

	virtual void visit(Apple* apple) = 0;
	virtual void visit(Book* book) = 0;
protected:
	std::string name;
};

//Concrete visitor:Customer
class Customer : public Visitor 
{
public:
	void visit(Apple* apple) 
	{
		std::cout << "顧客" << name << "選蘋果。" << std::endl;
	}

	void visit(Book* book) 
	{
		std::cout << "顧客" << name << "買書。" << std::endl;
	}
};

//Concrete visitor:Saler 
class Saler : public Visitor 
{
public:
	void visit(Apple* apple) 
	{
		std::cout << "收銀員" << name << "給蘋果過秤,然後計算價格。" << std::endl;
	}

	void visit(Book* book) 
	{
		std::cout << "收銀員" << name << "直接計算書的價格。" << std::endl;
	}
};

//abstract element: Product(商品類)
class Product 
{
public:
	virtual void accept(Visitor* visitor) = 0;
};

//concrete element:Apple
class Apple : public Product 
{
public:
	void accept(Visitor* visitor) 
	{
		visitor->visit(this);
	}
};

//concrete element:Book
class Book : public Product 
{
public:
	void accept(Visitor* visitor) 
	{
		visitor->visit(this);
	}
};

//objectstruct:BuyBasket
class BuyBasket 
{
public:
	void accept(Visitor* visitor) 
	{
		for (auto i : list) 
		{
			i->accept(visitor);
		}
	}

	void addProduct(Product* product) 
	{
		list.push_back(product);
	}

	void removeProduct(Product* product) 
	{
		list.remove(product);
	}
private:
	std::list<Product*> list;
};

//client
int main(void)
{
	//create product 
	Book b1, b2;
	Apple a1;

	//create buy basket
	BuyBasket basket;

	//add product into buy basket
	basket.addProduct(&b1);
	basket.addProduct(&b2);
	basket.addProduct(&a1);

	//create customer
	Customer customer;
	customer.setName("馬大姐");

	//visit operator
	basket.accept(&customer);

	//delete product from buy basket
	basket.removeProduct(&b2);

	std::cout << "-----------------" << std::endl;

	//creae saler
	Saler saler;
	saler.setName("李大爺");

	//visit operator
	basket.accept(&saler);

	return 0;
}

運行結果:

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章