?? 前言:本文章為瑞_系列專欄之《23種設(shè)計模式》的訪問者模式篇。本文中的部分圖和概念等資料,來源于博主學(xué)習(xí)設(shè)計模式的相關(guān)網(wǎng)站《菜鳥教程 | 設(shè)計模式》和《黑馬程序員Java設(shè)計模式詳解》,特此注明。本文中涉及到的軟件設(shè)計模式的概念、背景、優(yōu)點、分類、以及UML圖的基本知識和設(shè)計模式的6大法則等知識,建議閱讀 《瑞_23種設(shè)計模式_概述》
本系列 - 設(shè)計模式 - 鏈接:《瑞_23種設(shè)計模式_概述》
??本系列 - 創(chuàng)建型模式 - 鏈接????單例模式:《瑞_23種設(shè)計模式_單例模式》
??工廠模式:《瑞_23種設(shè)計模式_工廠模式》
??原型模式:《瑞_23種設(shè)計模式_原型模式》
抽象工廠模式:《瑞_23種設(shè)計模式_抽象工廠模式》
?建造者模式:《瑞_23種設(shè)計模式_建造者模式》??本系列 - 結(jié)構(gòu)型模式 - 鏈接??
??代理模式:《瑞_23種設(shè)計模式_代理模式》
?適配器模式:《瑞_23種設(shè)計模式_適配器模式》
?裝飾者模式:《瑞_23種設(shè)計模式_裝飾者模式》
??橋接模式:《瑞_23種設(shè)計模式_橋接模式》
??外觀模式:《瑞_23種設(shè)計模式_外觀模式》
??組合模式:《瑞_23種設(shè)計模式_組合模式》
??享元模式:《瑞_23種設(shè)計模式_享元模式》??本系列 - 行為型模式 - 鏈接??
模板方法模式:《瑞_23種設(shè)計模式_模板方法模式》
??策略模式:《瑞_23種設(shè)計模式_策略模式》
??命令模式:《瑞_23種設(shè)計模式_命令模式》
?職責(zé)鏈模式:《瑞_23種設(shè)計模式_職責(zé)鏈模式》
??狀態(tài)模式:《瑞_23種設(shè)計模式_狀態(tài)模式》
?觀察者模式:《瑞_23種設(shè)計模式_觀察者模式》
?中介者模式:《瑞_23種設(shè)計模式_中介者模式》
?迭代器模式:《瑞_23種設(shè)計模式_迭代器模式》
?訪問者模式:《后續(xù)更新》
?備忘錄模式:《后續(xù)更新》
?解釋器模式:《后續(xù)更新》
1 訪問者模式(Visitor Pattern)
??在訪問者模式(Visitor Pattern)中,我們使用了一個訪問者類,它改變了元素類的執(zhí)行算法。通過這種方式,元素的執(zhí)行算法可以隨著訪問者改變而改變。這種類型的設(shè)計模式屬于行為型模式。根據(jù)模式,元素對象已接受訪問者對象,這樣訪問者對象就可以處理元素對象上的操作。
??瑞:行為型模式用于描述程序在運(yùn)行時復(fù)雜的流程控制,即描述多個類或?qū)ο笾g怎樣相互協(xié)作共同完成單個對象都無法單獨完成的任務(wù),它涉及算法與對象間職責(zé)的分配。
??瑞:行為型模式分為類行為模式和對象行為模式,前者采用繼承機(jī)制來在類間分派行為,后者采用組合或聚合在對象間分配行為。由于組合關(guān)系或聚合關(guān)系比繼承關(guān)系耦合度低,滿足“合成復(fù)用原則”,所以對象行為模式比類行為模式具有更大的靈活性。訪問者模式屬于:對象行為模式
1.1 介紹
-
意圖:主要將數(shù)據(jù)結(jié)構(gòu)與數(shù)據(jù)操作分離。
-
主要解決:穩(wěn)定的數(shù)據(jù)結(jié)構(gòu)和易變的操作耦合問題。
-
何時使用:需要對一個對象結(jié)構(gòu)中的對象進(jìn)行很多不同的并且不相關(guān)的操作,而需要避免讓這些操作"污染"這些對象的類,使用訪問者模式將這些封裝到類中。
-
如何解決:在被訪問的類里面加一個對外提供接待訪問者的接口。
-
關(guān)鍵代碼:在數(shù)據(jù)基礎(chǔ)類里面有一個方法接受訪問者,將自身引用傳入訪問者。
-
應(yīng)用實例:
??您在朋友家做客,您是訪問者,朋友接受您的訪問,您通過朋友的描述,然后對朋友的描述做出一個判斷,這就是訪問者模式。 -
優(yōu)點:
??1?? 符合單一職責(zé)原則。
??2?? 優(yōu)秀的擴(kuò)展性。
??3?? 靈活性。 -
缺點:
??1?? 具體元素對訪問者公布細(xì)節(jié),違反了迪米特原則。
??2?? 具體元素變更比較困難。
??3?? 違反了依賴倒置原則,依賴了具體類,沒有依賴抽象。 -
使用場景:
??1?? 對象結(jié)構(gòu)中對象對應(yīng)的類很少改變,但經(jīng)常需要在此對象結(jié)構(gòu)上定義新的操作。
??2?? 需要對一個對象結(jié)構(gòu)中的對象進(jìn)行很多不同的并且不相關(guān)的操作,而需要避免讓這些操作"污染"這些對象的類,也不希望在增加新操作時修改這些類。 -
注意事項:
??1?? 訪問者可以對功能進(jìn)行統(tǒng)一,可以做報表、UI、攔截器與過濾器。
1.2 概述
定義:封裝一些作用于某種數(shù)據(jù)結(jié)構(gòu)中的各元素的操作,它可以在不改變這個數(shù)據(jù)結(jié)構(gòu)的前提下定義作用于這些元素的新的操作。
??訪問者模式的主要思想是將一些作用于某種數(shù)據(jù)結(jié)構(gòu)中的各元素的操作封裝起來,使得可以在不改變數(shù)據(jù)結(jié)構(gòu)的前提下定義作用于這些元素的新的操作。
??在訪問者模式中,針對系統(tǒng)中擁有固定類型數(shù)的對象結(jié)構(gòu)(元素),在其內(nèi)提供一個accept()方法來接受訪問者對象的訪問。不同的訪問者對同一個元素的訪問內(nèi)容是不同的,這使得相同的元素集合可以產(chǎn)生不同的數(shù)據(jù)結(jié)果。
1.3 訪問者模式的結(jié)構(gòu)
- 訪問者模式主要包含以下角色:
??1?? 抽象訪問者(Visitor)角色:定義了對每一個元素(Element)
訪問的行為,它的參數(shù)就是可以訪問的元素,它的方法個數(shù)理論上來講與元素類個數(shù)(Element的實現(xiàn)類個數(shù))是一樣的,從這點不難看出,訪問者模式要求元素類的個數(shù)不能改變。
??2?? 具體訪問者(ConcreteVisitor)角色:給出對每一個元素類訪問時所產(chǎn)生的具體行為。
??3?? 抽象元素(Element)角色:定義了一個接受訪問者的方法(accept
),其意義是指,每一個元素都要可以被訪問者訪問。
??4?? 具體元素(ConcreteElement)角色: 提供接受訪問方法的具體實現(xiàn),而這個具體的實現(xiàn),通常情況下是使用訪問者提供的訪問該元素類的方法。
??5?? 對象結(jié)構(gòu)(Object Structure)角色:定義當(dāng)中所提到的對象結(jié)構(gòu),對象結(jié)構(gòu)是一個抽象表述,具體點可以理解為一個具有容器性質(zhì)或者復(fù)合對象特性的類,它會含有一組元素(Element
),并且可以迭代這些元素,供訪問者訪問。
1.4 訪問者模式的優(yōu)缺點
優(yōu)點:
-
擴(kuò)展性好
在不修改對象結(jié)構(gòu)中的元素的情況下,為對象結(jié)構(gòu)中的元素添加新的功能。 -
復(fù)用性好
通過訪問者來定義整個對象結(jié)構(gòu)通用的功能,從而提高復(fù)用程度。 -
分離無關(guān)行為
通過訪問者來分離無關(guān)的行為,把相關(guān)的行為封裝在一起,構(gòu)成一個訪問者,這樣每一個訪問者的功能都比較單一。
缺點:
-
對象結(jié)構(gòu)變化很困難
在訪問者模式中,每增加一個新的元素類,都要在每一個具體訪問者類中增加相應(yīng)的具體操作,這違背了“開閉原則”。 -
違反了依賴倒置原則
訪問者模式依賴了具體類,而沒有依賴抽象類。
1.5 訪問者模式的使用場景
- 對象結(jié)構(gòu)相對穩(wěn)定,但其操作算法經(jīng)常變化的程序。
- 對象結(jié)構(gòu)中的對象需要提供多種不同且不相關(guān)的操作,而且要避免讓這些操作的變化影響對象的結(jié)構(gòu)。
2 案例一
【案例】給寵物喂食
2.1 需求
??現(xiàn)在養(yǎng)寵物的人特別多,我們就以這個為例,當(dāng)然寵物還分為狗,貓等,要給寵物喂食的話,主人可以喂,其他人也可以喂食。
- 訪問者角色:給寵物喂食的人
- 具體訪問者角色:主人、其他人
- 抽象元素角色:動物抽象類
- 具體元素角色:寵物狗、寵物貓
- 結(jié)構(gòu)對象角色:主人家
??類圖如下:
2.2 代碼實現(xiàn)
??創(chuàng)建抽象訪問者接口
/**
* 抽象訪問者角色類
*
* @author LiaoYuXing-Ray
**/
public interface Person {
// 喂食寵物狗
void feed(Cat cat);
// 喂食寵物貓
void feed(Dog dog);
}
??創(chuàng)建不同的具體訪問者角色(主人和其他人),都需要實現(xiàn) Person
接口
/**
* 具體訪問者角色類(自己)
*
* @author LiaoYuXing-Ray
**/
public class Owner implements Person {
public void feed(Cat cat) {
System.out.println("主人喂食貓");
}
public void feed(Dog dog) {
System.out.println("主人喂食狗");
}
}
/**
* 具體訪問者角色類(其他人)
*
* @author LiaoYuXing-Ray
**/
public class Someone implements Person {
public void feed(Cat cat) {
System.out.println("其他人喂食貓");
}
public void feed(Dog dog) {
System.out.println("其他人喂食狗");
}
}
??定義抽象節(jié)點 – 寵物
/**
* 抽象元素角色類
*
* @author LiaoYuXing-Ray
**/
public interface Animal {
// 接受訪問者訪問的功能
void accept(Person person);
}
??定義實現(xiàn)Animal
接口的 具體節(jié)點(元素)
/**
* 具體元素角色類(寵物貓)
*
* @author LiaoYuXing-Ray
**/
public class Cat implements Animal {
public void accept(Person person) {
person.feed(this);
// 訪問者給寵物貓喂食
System.out.println("好好吃,喵喵喵。。。");
}
}
/**
* 具體元素角色類(寵物狗)
*
* @author LiaoYuXing-Ray
**/
public class Dog implements Animal {
public void accept(Person person) {
person.feed(this);
// 訪問者給寵物貓喂食
System.out.println("好好吃,汪汪汪。。。");
}
}
??定義對象結(jié)構(gòu),此案例中就是主人的家
import java.util.ArrayList;
import java.util.List;
/**
* 對象結(jié)構(gòu)類
*
* @author LiaoYuXing-Ray
**/
public class Home {
// 聲明一個集合對象,用來存儲元素對象
private final List<Animal> nodeList = new ArrayList<Animal>();
// 添加元素功能
public void add(Animal animal) {
nodeList.add(animal);
}
public void action(Person person) {
// 遍歷集合,獲取每一個元素,讓訪問者訪問每一個元素
for (Animal animal : nodeList) {
animal.accept(person);
}
}
}
/**
* 測試類
*
* @author LiaoYuXing-Ray
**/
public class Client {
public static void main(String[] args) {
// 創(chuàng)建Home對象
Home home = new Home();
//添加元素到Home對象中
home.add(new Dog());
home.add(new Cat());
// 創(chuàng)建主人對象
Owner owner = new Owner();
Someone someone = new Someone();
// 讓主人喂食所有的寵物
home.action(owner);
home.action(someone);
}
}
??代碼運(yùn)行結(jié)果如下:
主人喂食狗
好好吃,汪汪汪。。。
主人喂食貓
好好吃,喵喵喵。。。
其他人喂食狗
好好吃,汪汪汪。。。
其他人喂食貓
好好吃,喵喵喵。。。
3 案例二
本案例為菜鳥教程中的案例
3.1 需求
??我們將創(chuàng)建一個定義接受操作的 ComputerPart 接口。Keyboard、Mouse、Monitor 和 Computer 是實現(xiàn)了 ComputerPart 接口的實體類。我們將定義另一個接口 ComputerPartVisitor,它定義了訪問者類的操作。Computer 使用實體訪問者來執(zhí)行相應(yīng)的動作。
??VisitorPatternDemo,我們的演示類使用 Computer、ComputerPartVisitor 類來演示訪問者模式的用法。
3.2 代碼實現(xiàn)
步驟 1
??定義一個表示元素的接口。
public interface ComputerPart {
public void accept(ComputerPartVisitor computerPartVisitor);
}
步驟 2
??創(chuàng)建擴(kuò)展了上述類的實體類。
public class Keyboard implements ComputerPart {
@Override
public void accept(ComputerPartVisitor computerPartVisitor) {
computerPartVisitor.visit(this);
}
}
public class Monitor implements ComputerPart {
@Override
public void accept(ComputerPartVisitor computerPartVisitor) {
computerPartVisitor.visit(this);
}
}
public class Mouse implements ComputerPart {
@Override
public void accept(ComputerPartVisitor computerPartVisitor) {
computerPartVisitor.visit(this);
}
}
public class Computer implements ComputerPart {
ComputerPart[] parts;
public Computer(){
parts = new ComputerPart[] {new Mouse(), new Keyboard(), new Monitor()};
}
@Override
public void accept(ComputerPartVisitor computerPartVisitor) {
for (int i = 0; i < parts.length; i++) {
parts[i].accept(computerPartVisitor);
}
computerPartVisitor.visit(this);
}
}
步驟 3
??定義一個表示訪問者的接口。
public interface ComputerPartVisitor {
public void visit(Computer computer);
public void visit(Mouse mouse);
public void visit(Keyboard keyboard);
public void visit(Monitor monitor);
}
步驟 4
??創(chuàng)建實現(xiàn)了上述類的實體訪問者。
public class ComputerPartDisplayVisitor implements ComputerPartVisitor {
@Override
public void visit(Computer computer) {
System.out.println("Displaying Computer.");
}
@Override
public void visit(Mouse mouse) {
System.out.println("Displaying Mouse.");
}
@Override
public void visit(Keyboard keyboard) {
System.out.println("Displaying Keyboard.");
}
@Override
public void visit(Monitor monitor) {
System.out.println("Displaying Monitor.");
}
}
步驟 5
??使用 ComputerPartDisplayVisitor 來顯示 Computer 的組成部分。
public class VisitorPatternDemo {
public static void main(String[] args) {
ComputerPart computer = new Computer();
computer.accept(new ComputerPartDisplayVisitor());
}
}
步驟 6
??執(zhí)行程序,輸出結(jié)果:
Displaying Mouse.
Displaying Keyboard.
Displaying Monitor.
Displaying Computer.
4 拓展——雙分派
訪問者模式用到了一種雙分派的技術(shù)。
4.1 分派
??變量被聲明時的類型叫做變量的靜態(tài)類型,有些人又把靜態(tài)類型叫做明顯類型;而變量所引用的對象的真實類型又叫做變量的實際類型。比如 Map map = new HashMap()
,map變量的靜態(tài)類型是 Map
,實際類型是 HashMap
。根據(jù)對象的類型而對方法進(jìn)行的選擇,就是分派(Dispatch),分派(Dispatch)又分為兩種,即靜態(tài)分派和動態(tài)分派。
-
靜態(tài)分派(Static Dispatch) :發(fā)生在編譯時期,分派根據(jù)靜態(tài)類型信息發(fā)生。靜態(tài)分派對于我們來說并不陌生,方法重載就是靜態(tài)分派。
-
動態(tài)分派(Dynamic Dispatch): 發(fā)生在運(yùn)行時期,動態(tài)分派動態(tài)地置換掉某個方法。Java通過方法的重寫(多態(tài))支持動態(tài)分派。
4.2 動態(tài)分派(多態(tài))
通過方法的重寫(多態(tài))支持動態(tài)分派。
public class Animal {
public void execute() {
System.out.println("Animal");
}
}
public class Dog extends Animal {
@Override
public void execute() {
System.out.println("dog");
}
}
public class Cat extends Animal {
@Override
public void execute() {
System.out.println("cat");
}
}
public class Client {
public static void main(String[] args) {
Animal a = new Dog();
a.execute();
Animal a1 = new Cat();
a1.execute();
}
}
??執(zhí)行程序,輸出結(jié)果:
dog
cat
??上面代碼的結(jié)果大家應(yīng)該直接可以說出來,這不就是多態(tài)嗎!運(yùn)行執(zhí)行的是子類中的方法。
??Java編譯器在編譯時期并不總是知道哪些代碼會被執(zhí)行,因為編譯器僅僅知道對象的靜態(tài)類型,而不知道對象的真實類型;而方法的調(diào)用則是根據(jù)對象的真實類型,而不是靜態(tài)類型。
4.3 靜態(tài)分派(重載)
通過方法重載支持靜態(tài)分派。
public class Animal {
}
public class Dog extends Animal {
}
public class Cat extends Animal {
}
public class Execute {
public void execute(Animal a) {
System.out.println("Animal");
}
public void execute(Dog d) {
System.out.println("dog");
}
public void execute(Cat c) {
System.out.println("cat");
}
}
public class Client {
public static void main(String[] args) {
Animal a = new Animal();
Animal a1 = new Dog();
Animal a2 = new Cat();
Execute exe = new Execute();
exe.execute(a);
exe.execute(a1);
exe.execute(a2);
}
}
??執(zhí)行程序,輸出結(jié)果:
Animal
Animal
Animal
??這個結(jié)果可能出乎一些你的意料了吧~
??因為重載方法的分派是根據(jù)靜態(tài)類型進(jìn)行的,這個分派過程在編譯時期就完成了。
4.4 雙分派
所謂雙分派技術(shù)就是在選擇一個方法的時候,不僅僅要根據(jù)消息接收者(receiver)的運(yùn)行時區(qū)別,還要根據(jù)參數(shù)的運(yùn)行時區(qū)別。
public class Animal {
public void accept(Execute exe) {
exe.execute(this);
}
}
public class Dog extends Animal {
public void accept(Execute exe) {
exe.execute(this);
}
}
public class Cat extends Animal {
public void accept(Execute exe) {
exe.execute(this);
}
}
public class Execute {
public void execute(Animal a) {
System.out.println("animal");
}
public void execute(Dog d) {
System.out.println("dog");
}
public void execute(Cat c) {
System.out.println("cat");
}
}
public class Client {
public static void main(String[] args) {
Animal a = new Animal();
Animal d = new Dog();
Animal c = new Cat();
Execute exe = new Execute();
a.accept(exe);
d.accept(exe);
c.accept(exe);
}
}
??在上面代碼中,客戶端將 Execute 對象做為參數(shù)傳遞給 Animal 類型的變量調(diào)用的方法,這里完成第一次分派,這里是方法重寫,所以是動態(tài)分派,也就是執(zhí)行實際類型中的方法,同時也將自己this作為參數(shù)傳遞進(jìn)去,這里就完成了第二次分派
,這里的 Execute 類中有多個重載的方法,而傳遞進(jìn)行的是this,就是具體的實際類型的對象。
??說到這里,我們已經(jīng)明白雙分派是怎么回事了,但是它有什么效果呢?就是可以實現(xiàn)方法的動態(tài)綁定,我們可以對上面的程序進(jìn)行修改。運(yùn)行結(jié)果如下:
??執(zhí)行程序,輸出結(jié)果:
animal
dog
cat
雙分派實現(xiàn)動態(tài)綁定的本質(zhì),就是在重載方法委派的前面加上了繼承體系中覆蓋的環(huán)節(jié),由于覆蓋是動態(tài)的,所以重載就是動態(tài)的了文章來源:http://www.zghlxwxcb.cn/news/detail-851648.html
??如果覺得這篇文章對您有所幫助的話,請動動小手點波關(guān)注??,你的點贊??收藏??轉(zhuǎn)發(fā)??評論??都是對博主最好的支持~文章來源地址http://www.zghlxwxcb.cn/news/detail-851648.html
到了這里,關(guān)于瑞_23種設(shè)計模式_訪問者模式的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!