小白設計模式:訪問者模式

定義

可做用於對象結構中各個元素,在不改變各元素類的前提下,定義做用於這些元素新操做方法的一種行爲型設計模式。設計模式

主要組成

抽象訪問者(Visitor): 聲明出對對象結構中每個具體元素的訪問方法visit,傳入Concrete Element對象做爲參數bash

具體訪問者(Concrete Visitor): 實現各類visit方法,調用具體元素對象完成對應的各類操做微信

元素(Element): 定義出抽象accept方法,用於接收Visitor對象參數框架

具體元素(Concrete Element): 實現accept操做,該操做通常用於操做執行Visitor.visit方法,將自身做爲參數傳入ide

對象結構(Object structure): 各個元素構成的一個總體,提供可以讓訪問者訪問全部元素的接口。能夠是集合(好比List),或者是複合的類對象。ui

UML圖

框架代碼

抽象訪問者: 須要依賴於具體的元素類,而不是抽象元素類,以免if-else之類的嵌套和類型判斷this

public interface Visitor {
	
	void visit(ConcreteElementA elementA);
	void visit(ConcreteElementB elementB);
}
複製代碼

具體訪問者:spa

public class ConcreteVisitorA implements Visitor{

	@Override
	public void visit(ConcreteElementA elementA) {
		// use elementA to do something
	}

	@Override
	public void visit(ConcreteElementB elementB) {
		// use elementB to do something
		
	}
}

public class ConcreteVisitorB implements Visitor{

	@Override
	public void visit(ConcreteElementA elementA) {
		// use elementA to do something
	}

	@Override
	public void visit(ConcreteElementB elementB) {
		// use elementB to do something
		
	}
}
複製代碼

抽象元素(可能接口或者抽象類): 由各個具體元素類實現accept接口,用於使得Visitor依賴於具體元素類設計

public interface Element {
	
	void accept(Visitor visitor);
}
複製代碼

具體元素:code

public class ConcreteElementA implements Element{

	@Override
	public void accept(Visitor visitor) {
		visitor.visit(this);
	}

}

public class ConcreteElementB implements Element{

	@Override
	public void accept(Visitor visitor) {
		visitor.visit(this);
	}

}
複製代碼

Client中簡單調用:

List<Element> elements = new ArrayList<>();
    elements.add(new ConcreteElementA());
    elements.add(new ConcreteElementB());
    elements.add(new ConcreteElementA());
    
    ConcreteVisitorA concreteVisitorA = new ConcreteVisitorA();
    ConcreteVisitorB concreteVisitorB = new ConcreteVisitorB();
    for (Element element : elements) {
    	element.accept(concreteVisitorA);
    	element.accept(concreteVisitorB);
    }
複製代碼

具體例子

公司對員工進行考覈,員工存在工程師和產品經理,考覈的評審有CEO和CTO,CTO只關注工程師的代碼量和產品經理的新產品數量。而CEO則關注工程師的KPI和產品經理的KPI及新產品數量。從這邊能夠看出CEO和CTO對員工考覈的關注點不同,這就須要對員工類型進行處理,就能夠用到訪問者模式(員工分類結構也是很穩定不變的)。(來自<<Android源碼設計模式解析與實踐>>)

UML

代碼

員工Staff:

public abstract class Staff {
	String name;
	public Staff(String name) {
		this.name = name;
	}
	
	public String getName() {
		return name;
	}
	
	public abstract int getKPI();
	public abstract void accept(Visitor visitor);
}
複製代碼

工程師Engineer:

public class Engineer extends Staff{

	public Engineer(String name) {
		super(name);
		
	}

	@Override
	public int getKPI() {
		return 9;
	}
	
	@Override
	public void accept(Visitor visitor) {
		visitor.visit(this);
	}
	
	public int getCodeLines() {
		return 9999;
	}

}
複製代碼

產品經理Manager:

public class Manager extends Staff{

	public Manager(String name) {
		super(name);
		
	}

	@Override
	public int getKPI() {
		return 10;
	}

	@Override
	public void accept(Visitor visitor) {
		visitor.visit(this);
	}
	
	public int getProductCount() {
		return 5;
	}
}
複製代碼

訪問者Visitor:

public interface Visitor {
	
	void visit(Engineer engineer);
	void visit(Manager manager);
}
複製代碼

CEO:

public class CEOVisitor implements Visitor{

	@Override
	public void visit(Engineer engineer) {
		System.out.println("CEOVisitor 考覈工程師"+ engineer.getName() + ",KPI:" + engineer.getKPI());
		
	}

	@Override
	public void visit(Manager manager) {
		// TODO Auto-generated method stub
		System.out.println("CEOVisitor 考覈產品經理"+ manager.getName() + ",KPI:" + manager.getKPI() + ",產品數:" + manager.getProductCount());
	}

}
複製代碼

CTO:

public class CTOVisitor implements Visitor{
	
	@Override
	public void visit(Engineer engineer) {
		System.out.println("CTOVisitor 考覈工程師"+ engineer.getName() + ",CodeLines:" + engineer.getCodeLines());
		
	}

	@Override
	public void visit(Manager manager) {
		// TODO Auto-generated method stub
		System.out.println("CTOVisitor 考覈產品經理"+ manager.getName() + ",產品數:" + manager.getProductCount());
	}
}
複製代碼

BussinessReport(對應Object structure):

public class BussinessReport {
	List<Staff> staffs = new ArrayList<>();

	public BussinessReport() {
		staffs.add(new Engineer("小張"));
		staffs.add(new Manager("小王"));
		
	}
	
	public void showReport(Visitor visitor) {
		for (Staff staff : staffs) {
			staff.accept(visitor);
		}
	}
}
複製代碼

Client調用:

//構建報表
BussinessReport bussinessReport = new BussinessReport();
//給CEO看
bussinessReport.showReport(new CEOVisitor());
//給CTO看
bussinessReport.showReport(new CTOVisitor());
複製代碼

假設不使用訪問者模式

若是不使用訪問者模式的話,也就不存在Visitor繼承體系,以上述具體例子爲例則須要對Staff的各類類型進行判斷: 假設在ReportUtils處理:

public class ReportUtils {
    public static void visitCEO(Staff staff) {
        if (staff instanceof Manager) {
            Manager manager = (Manager)staff;
            System.out.println("考覈產品經理"+ manager.getName() + ",產品數:" + manager.getProductCount());
        } else if(staff instanceof Engineer) {
            Engineer engineer = (Engineer)staff;
            System.out.println("考覈工程師"+ engineer.getName() + ",KPI:" + engineer.getKPI());
        }
    }
    
    public static void visitCTO(Staff staff) {
        if (staff instanceof Manager) {
            Manager manager = (Manager)staff;
            System.out.println("考覈產品經理"+ manager.getName() + ",產品數:" + manager.getProductCount());
        } else if(staff instanceof Engineer) {
            Engineer engineer = (Engineer)staff;
    		System.out.println("考覈工程師"+ engineer.getName() + ",CodeLines:" + engineer.getCodeLines());
        }
    }
}
複製代碼

這就會致使出現一大堆的if -else嵌套和類型轉換,當類型較多的時候就會難以擴展和維護,而且當關注層面不一樣,重複的判斷會不少,難以維護後續持續擴展新的操做(visitXX,visitXXX...)。而使用訪問者Visitor模式,可以經過accept-visit方法的配套使用使得不一樣關注面的操做能夠分離,而且去除一大堆嵌套if-else和類型轉換的判斷,靈活性更好,可擴展性高,更易於維護。

總結

優勢

角色職責分離,符合單一職責原則;

容許對組合結構中的各個元素加入新的操做,而不須要修改結構自己,增長新操做相對容易;

集中管理訪問者所進行操做的代碼;

缺點

具體元素元素對訪問者公佈細節,違反了迪米特原則(一個類對本身須要耦合或調用的類知道的最少);

增長刪減具體元素時修改爲本過大,須要修改Visitor繼承體系;

爲了達到"區別對待"而依賴具體類而不是抽象,違反了"依賴倒置"原則(高層模塊不該該依賴低層模塊,二者都應該依賴抽象);

應用場景

一、對象結構中對象對應的類不多改變,但常常須要在此對象結構上定義新的操做。

二、須要對一個對象結構中的對象進行不少不一樣的而且不相關的操做,而須要避免讓這些操做"污染"這些對象的類,也不但願在增長新操做時修改這些類。 注意事項:訪問者能夠對功能進行統一,能夠作報表、UI、攔截器與過濾器。

微信公衆號

相關文章
相關標籤/搜索