引言
??23設(shè)計模式這是最后一篇了,到此就結(jié)尾了,先回顧一下上一篇所講的解釋器模式,然后看看今天的訪問者模式。
示例地址
??Demo
類圖
定義
??封裝一些作用于某些數(shù)據(jù)結(jié)構(gòu)的各元素的操作,它可以在不改變這個數(shù)據(jù)結(jié)構(gòu)的前提下定義作用于這些元素的新的操作。
使用場景
??1. 對象結(jié)構(gòu)比較穩(wěn)定,但經(jīng)常需要在此對象結(jié)構(gòu)上定義新的操作。
??2. 需要對一個對象結(jié)構(gòu)中的對象進(jìn)行很多不同的并且不相關(guān)的操作,而需要避免這些操作“污染”這些對象的類,也不希望在增加新操作時修改這些類。
訪問者模式中的角色
??1. Vistor(抽象訪問者):抽象訪問者為對象結(jié)構(gòu)中每一個具體元素類ConcreteElement聲明一個訪問操作,從這個操作的名稱或參數(shù)類型可以清楚知道需要訪問的具體元素的類型,具體訪問者需要實現(xiàn)這些操作方法,定義對這些元素的訪問操作。
??2. ConcreteVisitor(具體訪問者):具體訪問者實現(xiàn)了每個由抽象訪問者聲明的操作,每一個操作用于訪問對象結(jié)構(gòu)中一種類型的元素。
??3. Element(抽象元素):抽象元素一般是抽象類或者接口,它定義一個accept()方法,該方法通常以一個抽象訪問者作為參數(shù)。
??4. ConcreteElement(具體元素):具體元素實現(xiàn)了accept()方法,在accept()方法中調(diào)用訪問者的訪問方法以便完成對一個元素的操作。
??5. ObjectStructure(對象結(jié)構(gòu)):對象結(jié)構(gòu)是一個元素的集合,它用于存放元素對象,并且提供了遍歷其內(nèi)部元素的方法。它可以結(jié)合組合模式來實現(xiàn),也可以是一個簡單的集合對象,如一個List對象或一個Set對象。
訪問者模式示例
1. Visitor抽象類
/**
* @author 512573717@qq.com
* @created 2018/8/20 上午10:35.
*/
public interface Visitor {
void visit(ConcreteElementA elementA);
void visit(ConcreteElementB elementB);
}
2. Visitor實現(xiàn)類ConcreteVisitorA
/**
* @author 512573717@qq.com
* @created 2018/8/20 上午10:36.
*/
public class ConcreteVisitorA implements Visitor {
@Override
public void visit(ConcreteElementA elementA) {
}
@Override
public void visit(ConcreteElementB elementB) {
}
}
3. Visitor實現(xiàn)類ConcreteVisitorB
/**
* @author 512573717@qq.com
* @created 2018/8/20 上午10:36.
*/
public class ConcreteVisitorB implements Visitor {
@Override
public void visit(ConcreteElementA elementA) {
}
@Override
public void visit(ConcreteElementB elementB) {
}
}
4. Element接口
/**
* @author 512573717@qq.com
* @created 2018/8/20 上午10:37.
*/
public interface Element {
void accept(Visitor visitor);
}
5. Element實現(xiàn)類ConcreteElementA
/**
* @author 512573717@qq.com
* @created 2018/8/20 上午10:37.
*/
public class ConcreteElementA implements Element {
public void accept(Visitor visitor) {
System.out.println(visitor.getClass().getCanonicalName() + "=====訪問" +"ConcreteElementA");
visitor.visit(this);
}
}
6. Element實現(xiàn)類ConcreteElementB
/**
* @author 512573717@qq.com
* @created 2018/8/20 上午10:37.
*/
public class ConcreteElementB implements Element {
public void accept(Visitor visitor) {
System.out.println(visitor.getClass().getCanonicalName() + "=======訪問"+ "ConcreteElementB");
visitor.visit(this);
}
}
7. ObjectStructure
/**
* @author 512573717@qq.com
* @created 2018/8/20 上午10:39.
*/
public class ObjectStructure {
private List<Element> mElements = new ArrayList<>();
public void addElement(Element e) {
mElements.add(e);
}
public void removeElement(Element e) {
mElements.remove(e);
}
public void accpet(Visitor visitor) {
for (Element e : mElements) {
e.accept(visitor);
}
}
}
8. Client
ObjectStructure os = new ObjectStructure();
os.addElement(new ConcreteElementA());
os.addElement(new ConcreteElementB());
//創(chuàng)建一個訪問者
Visitor visitor = new ConcreteVisitorA();
os.accpet(visitor);
訪問者模式中的偽動態(tài)雙分派
-
靜態(tài)分派
??靜態(tài)分派就是按照變量的靜態(tài)類型進(jìn)行分派,從而確定方法的執(zhí)行版本,靜態(tài)分派在編譯時期就可以確定方法的版本。靜態(tài)分派最典型的應(yīng)用就是方法重載。
public class StaticDispatch {
public void test(String string){
System.out.println("this is string");
}
public void test(Integer integer){
System.out.println("this is integer");
}
public static void staticDispatch(String[] args) {
String s1 ="aaaaa";
Integer a = 1;
StaticDispatch staticDispatch = new StaticDispatch();
staticDispatch.test(a);
staticDispatch.test(s1);
}
}
-
動態(tài)分派
??與靜態(tài)相反,它不是在編譯期確定的方法版本,而是在運(yùn)行時才能確定。而動態(tài)分派最典型的應(yīng)用就是多態(tài)的特性。
public interface Animal{
void test();
}
public class Dog implements Animal{
public void test(){
System.out.println("dog");
}
}
public class Cat implements Animal{
public void test(){
System.out.println("cat");
}
}
public class DynamicDispatch {
public static void main(String[] args) {
Animal dog = new Dog();
Animal cat = new Cat();
dog.test();
cat.test();
}
}
- 偽動態(tài)雙分派
??訪問者模式中使用的是偽動態(tài)雙分派,所謂的動態(tài)雙分派就是在運(yùn)行時依據(jù)兩個實際類型去判斷一個方法的運(yùn)行行為,而訪問者模式實現(xiàn)的手段是進(jìn)行了兩次動態(tài)單分派來達(dá)到這個效果。
public void accpet(Visitor visitor) {
for (Element e : mElements) {
e.accept(visitor);
}
}
訪問者模式實際運(yùn)用
1. Visitor
/**
* Visitor
*
* @author 512573717@qq.com
* @created 2018/8/20 下午12:46.
*/
public interface AccountBookViewer {
//查看消費(fèi)的單子
void view(ConsumeBill bill);
//查看收入的單子
void view(IncomeBill bill);
}
2. 老板
/**
* visitor 老板
*
* @author 512573717@qq.com
* @created 2018/8/20 下午12:47.
*
*/
public class Boss implements AccountBookViewer {
private double totalIncome;
private double totalConsume;
//老板只關(guān)注一共花了多少錢以及一共收入多少錢,其余并不關(guān)心
public void view(ConsumeBill bill) {
totalConsume += bill.getAmount();
}
public void view(IncomeBill bill) {
totalIncome += bill.getAmount();
}
public double getTotalIncome() {
System.out.println("老板查看一共收入多少,數(shù)目是:" + totalIncome);
return totalIncome;
}
public double getTotalConsume() {
System.out.println("老板查看一共花費(fèi)多少,數(shù)目是:" + totalConsume);
return totalConsume;
}
}
3. 會計
/**
* visitor 會計
*
* @author 512573717@qq.com
* @created 2018/8/20 下午12:47.
*/
public class CPA implements AccountBookViewer {
//注會在看賬本時,如果是支出,則如果支出是工資,則需要看應(yīng)該交的稅交了沒
public void view(ConsumeBill bill) {
if (bill.getItem().equals("工資")) {
System.out.println("注會查看工資是否交個人所得稅。");
}
}
//如果是收入,則所有的收入都要交稅
public void view(IncomeBill bill) {
System.out.println("注會查看收入交稅了沒。");
}
}
4. Element
/**
* Element
*
* @author 512573717@qq.com
* @created 2018/8/20 下午12:49.
*/
public interface Bill {
void accept(AccountBookViewer viewer);
}
5. 消費(fèi)
/**
* 消費(fèi)
*
* @author 512573717@qq.com
* @created 2018/8/20 下午12:50.
*/
public class ConsumeBill implements Bill {
private double amount;
private String item;
public ConsumeBill(double amount, String item) {
super();
this.amount = amount;
this.item = item;
}
public void accept(AccountBookViewer viewer) {
viewer.view(this);
}
public double getAmount() {
return amount;
}
public String getItem() {
return item;
}
}
6. 收入的單子
/**
* 收入的單子
*
* @author 512573717@qq.com
* @created 2018/8/20 下午12:51.
*/
public class IncomeBill implements Bill{
private double amount;
private String item;
public IncomeBill(double amount, String item) {
super();
this.amount = amount;
this.item = item;
}
public void accept(AccountBookViewer viewer) {
viewer.view(this);
}
public double getAmount() {
return amount;
}
public String getItem() {
return item;
}
}
7. 賬簿
/**
* ObjectStruture
*
* @author 512573717@qq.com
* @created 2018/8/20 下午12:52.
*/
public class AccountBook {
//單子列表
private List<Bill> billList = new ArrayList<Bill>();
//添加單子
public void addBill(Bill bill) {
billList.add(bill);
}
//供賬本的查看者查看賬本
public void show(AccountBookViewer viewer) {
for (Bill bill : billList) {
bill.accept(viewer);
}
}
}
8. Client
AccountBook accountBook = new AccountBook();
//添加收入
accountBook.addBill(new IncomeBill(10000, "賣商品"));
//添加支出
accountBook.addBill(new ConsumeBill(1000, "工資"));
AccountBookViewer boss = new Boss();
AccountBookViewer cpa = new CPA();
//兩個訪問者分別訪問賬本
accountBook.show(cpa);
accountBook.show(boss);
((Boss) boss).getTotalConsume();
((Boss) boss).getTotalIncome();
總結(jié)
-
優(yōu)點(diǎn)
- 使得對象結(jié)構(gòu)和作用于結(jié)構(gòu)上的操作解耦,使得操作集合可以獨(dú)立變化。
添加新的操作或者說訪問者會非常容易,無須修改源代碼,符合“開閉原則”。 - 將有關(guān)元素對象的操作集中到一個訪問者對象中,而不是分散在一個個的元素類中。類的職責(zé)更加清晰,有利于對象結(jié)構(gòu)中元素對象的復(fù)用,相同的對象結(jié)構(gòu)可以供多個不同的訪問者訪問。
- 使得類層次結(jié)構(gòu)不改變的情況下,可以針對各個層次做出不同的操作,而不影響類層次結(jié)構(gòu)的完整性。
- 可以跨越類層次結(jié)構(gòu),訪問不同層次的元素類,做出相應(yīng)的操作。
-
缺點(diǎn)
- 增加新的元素會非常困難。在訪問者模式中,每增加一個新的元素類都意味著要在抽象訪問者角色中增加一個新的抽象操作,并在每一個具體訪問者類中增加相應(yīng)的具體操作,這違背了“開閉原則”的要求。
- 實現(xiàn)起來比較復(fù)雜,會增加系統(tǒng)的復(fù)雜性。
- 破壞封裝,如果將訪問行為放在各個元素中,則可以不暴露元素的內(nèi)部結(jié)構(gòu)和狀態(tài),但使用訪問者模式的時候,為了讓訪問者能獲取到所關(guān)心的信息,元素類不得不暴露出一些內(nèi)部的狀態(tài)和結(jié)構(gòu)。