設計模式之(八)訪問者模式

本文首發於我的博客html

前言

本文代碼爲java代碼java

什麼是訪問者模式

訪問者模式 屬於行爲型模式,在菜鳥教程中的定義以下node

在訪問者模式(Visitor Pattern)中,咱們使用了一個訪問者類,它改變了元素類的執行算法。經過這種方式,元素的執行算法能夠隨着訪問者改變而改變。這種類型的設計模式屬於行爲型模式。根據模式,元素對象已接受訪問者對象,這樣訪問者對象就能夠處理元素對象上的操做。git

模式結構

介紹

  • 意圖:主要將數據結構與數據操做分離。

主要解決:

  • 穩定的數據結構和易變的操做耦合問題。

什麼時候使用:

  • 須要對一個對象結構中的對象進行不少不一樣的而且不相關的操做,而須要避免讓這些操做"污染"這些對象的類,使用訪問者模式將這些封裝到類中。

如何解決:

  • 在被訪問的類裏面加一個對外提供接待訪問者的接口。

關鍵代碼:

  • 在數據基礎類裏面有一個方法接受訪問者,將自身引用傳入訪問者。

應用實例:

  • 您在朋友家作客,您是訪問者,朋友接受您的訪問,您經過朋友的描述,而後對朋友的描述作出一個判斷,這就是訪問者模式。

優勢:

  • 符合單一職責原則。
  • 優秀的擴展性。 三、靈活性。

缺點:

  • 具體元素對訪問者公佈細節,違反了迪米特原則。
  • 具體元素變動比較困難。
  • 違反了依賴倒置原則,依賴了具體類,沒有依賴抽象。

使用場景:

  • 對象結構中對象對應的類不多改變,但常常須要在此對象結構上定義新的操做。
  • 須要對一個對象結構中的對象進行不少不一樣的而且不相關的操做,而須要避免讓這些操做"污染"這些對象的類,也不但願在增長新操做時修改這些類。

注意事項:

  • 訪問者能夠對功能進行統一,能夠作報表、UI、攔截器與過濾器。

代碼

場景

假設咱們寫了一個平衡二叉樹,那麼對應的確定有對應的確定有添加元素,刪除元素,中序遍歷,後序遍歷...等等。咱們就以中序遍歷二叉樹爲例,先看不用訪問者模式會有什麼問題。github

Node

建立Node節點類,有左子樹,右子樹,父節點算法

public static class Node<E> {
		Integer element;
		Node<Integer> left;
		Node<Integer> right;
		Node<Integer> parent;
		public Node(Integer element, Node<Integer> parent) {
			this.element = element;
			this.parent = parent;
		}
	}
複製代碼

BinarySearchTree

建立BinarySearchTree類,有size,根節點root, 添加,刪除,遍歷等等設計模式

public class BinarySearchTree<Integer> {
	private int size;
	public Node<Integer> root;
	
	public void add(Integer element) {
		//添加的代碼就不寫了
	}
	
	public void remove(Integer element) {
		//刪除的代碼就不寫了	
	}
	
	... 其餘接口
	
	// 中序遍歷
	
	private void inorder(Node<Integer> node) {
		if (node == null ) return;
	
		// 遍歷左子樹
		inorder(node.left);
		// 打印
		System.out.println(node.element);
		
		// 遍歷右子樹
		inorder(node.right);
	}
}


複製代碼

存在的問題?

上面的代碼是否也是能夠用的,調用inorder遍歷以後,這個二叉樹能夠按照中序遍歷的方式打印出來,彷佛沒什麼問題。可是仔細想一想,其實存在問題的。由於這個咱們寫死了是打印出元素,假設咱們真正使用的時候,不是想直接打印呢?而是想每一個元素的值加上2 而後再打印呢?又或者每一個元素的值加上10,並且不想打印呢?bash

你可能會說,那簡單啊,直接改啊,例如每一個元素的值加上2 而後再打印數據結構

public class BinarySearchTree<Integer> {
	//... 其餘接口
	
	// 中序遍歷
	
	private void inorder(Node<Integer> node) {
		if (node == null ) return;
		// 遍歷左子樹
		inorder(node.left);
		
		// 打印
		System.out.println(node.element + 2);

		// 遍歷右子樹
		inorder(node.right);
	}
}


複製代碼

看起來也沒問題,既然是加上2再打印那就直接加上2再打印咯。可是有以下兩個問題ui

  • 很麻煩,每次不一樣的場景都要修改遍歷的實現
  • 若是是咱們提供給外界使用的話,儘可能不要讓使用者修改內部實現。

也就是說,有沒有一種不修改遍歷的具體實現,就能知足不一樣場景下的遍歷呢?答案是有的,就是訪問者模式

訪問者模式

public class BinarySearchTree<Integer> {
	private int size;
	public Node<Integer> root;
	
	public void add(Integer element) {
		//添加的代碼就不寫了
	}
	
	public void remove(Integer element) {
		//刪除的代碼就不寫了	
	}
	
	... 其餘接口
	
	// 中序遍歷
	public void inorder(Visitor<Integer> visitor) {
		if (visitor == null) return;
		inorder(root, visitor);
	}
	
	
	private void inorder(Node<Integer> node, Visitor<Integer> visitor) {
		if (node == null ) return;
		
		//遍歷左子樹
		inorder(node.left, visitor);
		
		// 元素給visitor,具體的邏輯由外界的visitor處理
		visitor.visit(node.element);
		
		//遍歷右子樹
		inorder(node.right, visitor);
	}
}


複製代碼

使用訪問者模式的調用

這樣修改以後,使用的時候

每一個節點的值加上2再打印

BinarySearchTree<Integer> bst = new BinarySearchTree<>();
bst.preorder(new Visitor<Integer>() {
	public boolean visit(Integer element) {
		System.out.print(element + 2);
	}
});
複製代碼

每一個節點的值加上10再打印

BinarySearchTree<Integer> bst = new BinarySearchTree<>();
bst.preorder(new Visitor<Integer>() {
	public boolean visit(Integer element) {
		System.out.print(element + 10);
	}
});
複製代碼

這樣的話,不管使用者如何更改需求,不一樣的場景下,都不用修改二叉樹內部的遍歷代碼,均可以知足。

相關文章
相關標籤/搜索