定義
封裝某些作用于某種數(shù)據(jù)結(jié)構(gòu)中各元素的操作前鹅,它可以在不改變數(shù)據(jù)結(jié)構(gòu)的前提下定義作用于這些元素的新的操作。
介紹
- 訪問(wèn)者模式屬于行為型模式聂渊。
- 訪問(wèn)者模式是一種將數(shù)據(jù)結(jié)構(gòu)和數(shù)據(jù)操作分離的設(shè)計(jì)模式。
- 訪問(wèn)者模式比較復(fù)雜,而且實(shí)際使用的地方并不多祠挫。
- 訪問(wèn)者模式適用于數(shù)據(jù)結(jié)構(gòu)穩(wěn)定的元素操作上,一旦數(shù)據(jù)結(jié)構(gòu)易變窗看,則不適用茸歧。
UML類(lèi)圖
角色說(shuō)明:
- Visitor(抽象訪問(wèn)者):接口或者抽象類(lèi),為每一個(gè)元素(Element)聲明一個(gè)訪問(wèn)的方法显沈。
- ConcreteVisitor(具體訪問(wèn)者):實(shí)現(xiàn)抽象訪問(wèn)者中的方法软瞎,即對(duì)每一個(gè)元素都有其具體的訪問(wèn)行為。
- Element(抽象元素):接口或者抽象類(lèi)拉讯,定義一個(gè)accept方法涤浇,能夠接受訪問(wèn)者(Visitor)的訪問(wèn)。
- ConcreteElementA魔慷、ConcreteElementB(具體元素):實(shí)現(xiàn)抽象元素中的accept方法只锭,通常是調(diào)用訪問(wèn)者提供的訪問(wèn)該元素的方法。
- Client(客戶端類(lèi)):即要使用訪問(wèn)者模式的地方院尔。
實(shí)現(xiàn)
以我們平時(shí)聽(tīng)歌看視頻為例蜻展,音樂(lè)視頻網(wǎng)站都會(huì)提供在線播放和下載的功能,當(dāng)我們有空的時(shí)候往往就選擇了在線播放邀摆,比較忙的時(shí)候就選擇先下載下來(lái)纵顾,有空的時(shí)候再去觀看。其中栋盹,音樂(lè)視頻網(wǎng)站就是具體的要訪問(wèn)的元素施逾,閑人和忙人就是具體的訪問(wèn)者,閑人和忙人的訪問(wèn)行為是不一樣的例获。
1汉额、創(chuàng)建抽象訪問(wèn)者。定義一個(gè)抽象的受訪問(wèn)方法以及其他公共的方法:
public abstract class Web {
public String name;
public Web(String name) {
this.name = name;
}
// 定義一個(gè)抽象的受訪問(wèn)方法
public abstract void accept(Visitor visitor);
// 下載資源
public abstract void download();
public String getName() {
return name;
}
}
2榨汤、創(chuàng)建具體元素蠕搜。實(shí)現(xiàn)抽象元素中的accept()方法,通常是調(diào)用訪問(wèn)者提供的訪問(wèn)該元素的方法件余。下面創(chuàng)建音樂(lè)類(lèi)以及視頻類(lèi)讥脐,他們都有一個(gè)download()方法遭居,但是其具體下載的內(nèi)容是不一樣的,同時(shí)他們也存在各自獨(dú)有的方法playMusic()和playVideo():
// 音樂(lè)類(lèi)
public class Music extends Web {
public Music(String name) {
super(name);
}
// 接受訪問(wèn)者的訪問(wèn)
@Override
public void accept(Visitor visitor) {
visitor.visit(this);
}
// 實(shí)現(xiàn)父類(lèi)中的公共方法
@Override
public void download() {
System.out.println("下載音樂(lè)~~");
}
// 音樂(lè)類(lèi)獨(dú)有方法
public void playMusic() {
System.out.println("播放音樂(lè)ing");
}
}
// 視頻類(lèi)
public class Video extends Web {
public Video(String name) {
super(name);
}
// 接受訪問(wèn)者的訪問(wèn)
@Override
public void accept(Visitor visitor) {
visitor.visit(this);
}
// 實(shí)現(xiàn)父類(lèi)中的公共方法
@Override
public void download() {
System.out.println("下載視頻~~");
}
// 視頻類(lèi)獨(dú)有方法
public void playVideo() {
System.out.println("播放視頻ing");
}
}
3旬渠、創(chuàng)建抽象訪問(wèn)者俱萍。為每一個(gè)元素聲明一個(gè)訪問(wèn)的方法:
public interface Visitor {
// 訪問(wèn)音樂(lè)類(lèi)
void visit(Music music);
// 訪問(wèn)視頻類(lèi)
void visit(Video video);
}
4、創(chuàng)建具體訪問(wèn)者告丢。實(shí)現(xiàn)抽象訪問(wèn)者中的方法枪蘑,即對(duì)每一個(gè)元素都有其具體的訪問(wèn)行為。下面以閑人和忙人為例:
// 閑人
public class Idler implements Visitor {
private String name;
public Idler(String name) {
this.name = name;
}
@Override
public void visit(Music music) {
System.out.println(name + "瀏覽音樂(lè)網(wǎng)站:" + music.getName());
music.playMusic();
}
@Override
public void visit(Video video) {
System.out.println(name + "瀏覽視頻網(wǎng)站:" + video.getName());
video.playVideo();
}
}
// 忙人
public class Busy implements Visitor {
private String name;
public Busy(String name) {
this.name = name;
}
@Override
public void visit(Music music) {
System.out.println(name + "瀏覽音樂(lè)網(wǎng)站:" + music.getName());
music.download();
}
@Override
public void visit(Video video) {
System.out.println(name + "瀏覽視頻網(wǎng)站:" + video.getName());
video.download();
}
}
5岖免、創(chuàng)建對(duì)象結(jié)構(gòu)岳颇。另外,為了方便訪問(wèn)多個(gè)元素颅湘,創(chuàng)建一個(gè)對(duì)象結(jié)構(gòu)话侧,在其內(nèi)部管理元素集合,并且可以迭代這些元素供訪問(wèn)者訪問(wèn):
public class Websites {
// 元素集合
List<Web> list = new ArrayList<>();
public void accept(Visitor visitor) {
Iterator<Web> iterator = list.iterator();
while (iterator.hasNext()) {
iterator.next().accept(visitor);
}
}
public void addWeb(Web web) {
list.add(web);
}
}
6闯参、客戶端測(cè)試
public void test() {
// 創(chuàng)建不同的元素
Music wangyiyue = new Music("網(wǎng)易云音樂(lè)");
Music kugou = new Music("酷狗");
Video youku = new Video("優(yōu)酷");
Video iqiyi = new Video("愛(ài)奇藝");
// 放入對(duì)象結(jié)構(gòu)中
Websites websites = new Websites();
websites.addWeb(wangyiyue);
websites.addWeb(kugou);
websites.addWeb(youku);
websites.addWeb(iqiyi);
// 集合接受idler1的訪問(wèn)
Visitor idler1 = new Idler("閑人1號(hào)");
websites.accept(idler1);
System.out.println("-------------------------------------");
// 集合接受busy1的訪問(wèn)
Visitor busy1 = new Busy("忙人2號(hào)");
websites.accept(busy1);
}
輸出結(jié)果:
閑人1號(hào)瀏覽音樂(lè)網(wǎng)站:網(wǎng)易云音樂(lè)
播放音樂(lè)ing
閑人1號(hào)瀏覽音樂(lè)網(wǎng)站:酷狗
播放音樂(lè)ing
閑人1號(hào)瀏覽視頻網(wǎng)站:優(yōu)酷
播放視頻ing
閑人1號(hào)瀏覽視頻網(wǎng)站:愛(ài)奇藝
播放視頻ing
-------------------------------------
忙人2號(hào)瀏覽音樂(lè)網(wǎng)站:網(wǎng)易云音樂(lè)
下載音樂(lè)~~
忙人2號(hào)瀏覽音樂(lè)網(wǎng)站:酷狗
下載音樂(lè)~~
忙人2號(hào)瀏覽視頻網(wǎng)站:優(yōu)酷
下載視頻~~
忙人2號(hào)瀏覽視頻網(wǎng)站:愛(ài)奇藝
下載視頻~~
應(yīng)用場(chǎng)景
- 對(duì)象結(jié)構(gòu)比較穩(wěn)定瞻鹏,很少改變,但是經(jīng)常需要在此對(duì)象結(jié)構(gòu)上定義新的操作行為時(shí)鹿寨。
- 需要對(duì)一個(gè)對(duì)象結(jié)構(gòu)中的對(duì)象進(jìn)行很多不同的并且不相關(guān)的操作新博,它可以在不改變這個(gè)數(shù)據(jù)結(jié)構(gòu)的前提下定義作用于這些元素的新的操作。
優(yōu)缺點(diǎn)
優(yōu)點(diǎn)
- 各種角色各司其職脚草,符合單一職責(zé)原則赫悄。
- 原有的類(lèi)上新增操作只需實(shí)現(xiàn)一個(gè)具體訪問(wèn)者就可以,不必修改整個(gè)類(lèi)層次馏慨,符合開(kāi)閉原則埂淮。
- 良好的擴(kuò)展性,新增訪問(wèn)操作變得簡(jiǎn)單写隶。
- 數(shù)據(jù)操作和數(shù)據(jù)結(jié)構(gòu)解耦同诫。
缺點(diǎn)
- 具體元素對(duì)訪問(wèn)者公布了實(shí)現(xiàn)細(xì)節(jié),破壞了類(lèi)的封裝性樟澜,違反了迪米特原則。
- 違反了依賴(lài)倒置原則叮盘,為了達(dá)到區(qū)別對(duì)待依賴(lài)了具體而不是抽象秩贰。
- 具體元素修改的成本太大。
- 新增具體元素困難柔吼,需要在抽象訪問(wèn)者角色中增加一個(gè)新的抽象操作毒费,違反了開(kāi)閉原則。
其他
訪問(wèn)者模式實(shí)際使用中比較少愈魏,但是真正需要用到時(shí)觅玻,還是很有用的想际。