點(diǎn)贊再看缀磕,養(yǎng)成習(xí)慣,公眾號(hào)搜一搜【一角錢(qián)技術(shù)】關(guān)注更多原創(chuàng)技術(shù)文章覆积。本文 GitHub org_hejianhui/JavaStudy 已收錄,有我的系列文章浅蚪。
前言
- 23種設(shè)計(jì)模式速記
- 工廠方法(factory method)模式
- 抽象工廠(abstract factory)模式
- 原型(prototype)模式
- 單例(singleton)模式
- 建造者/構(gòu)建器(builder)模式
- 適配器(adapter)模式
- 橋接(bridge)模式
- 組合(composite)模式
- 裝飾(decorator)模式
- 外觀(facade)模式
- 享元(flyweight)模式
- 代理(proxy)模式
- 責(zé)任鏈(chain of responsibility)模式
- 命令(command)模式
- 解釋器(interpreter)模式
- 迭代器(iterator)模式
- 中介者(mediator)模式
- 備忘錄(memento)模式
- 觀察者(observer)模式
- 狀態(tài)(state)模式
- 策略(strategy)模式
- 模版方法(template method)模式
23種設(shè)計(jì)模式快速記憶的請(qǐng)看上面第一篇皮胡,本篇和大家一起來(lái)學(xué)習(xí)訪問(wèn)者模式相關(guān)內(nèi)容。
這是23種設(shè)計(jì)模式的最后一個(gè)——訪問(wèn)者模式,這個(gè)模式確實(shí)不怎么好理解洪己,不怎么好用妥凳,而且實(shí)際中也很少用到這個(gè)設(shè)計(jì)模式。
在現(xiàn)實(shí)生活中答捕,有些集合對(duì)象中存在多種不同的元素绞愚,且每種元素也存在多種不同的訪問(wèn)者和處理方式。例如送悔,公園中存在多個(gè)景點(diǎn)玉转,也存在多個(gè)游客,不同的游客對(duì)同一個(gè)景點(diǎn)的評(píng)價(jià)可能不同沃琅;醫(yī)院醫(yī)生開(kāi)的處方單中包含多種藥元素哗咆,査看它的劃價(jià)員和藥房工作人員對(duì)它的處理方式也不同,劃價(jià)員根據(jù)處方單上面的藥品名和數(shù)量進(jìn)行劃價(jià)益眉,藥房工作人員根據(jù)處方單的內(nèi)容進(jìn)行抓藥晌柬。
這些被處理的數(shù)據(jù)元素相對(duì)穩(wěn)定而訪問(wèn)方式多種多樣的數(shù)據(jù)結(jié)構(gòu),如果用“訪問(wèn)者模式”來(lái)處理比較方便郭脂。訪問(wèn)者模式能把處理方法從數(shù)據(jù)結(jié)構(gòu)中分離出來(lái)年碘,并可以根據(jù)需要增加新的處理方法,且不用修改原來(lái)的程序代碼與數(shù)據(jù)結(jié)構(gòu)朱庆,這提高了程序的擴(kuò)展性和靈活性盛泡。
模式定義
將作用于某種數(shù)據(jù)結(jié)構(gòu)中的各元素的操作分離出來(lái)封裝成獨(dú)立的類(lèi),使其在不改變數(shù)據(jù)結(jié)構(gòu)的前提下可以添加作用于這些元素的新的操作娱颊,為數(shù)據(jù)結(jié)構(gòu)中的每個(gè)元素提供多種訪問(wèn)方式傲诵。它將對(duì)數(shù)據(jù)的操作與數(shù)據(jù)結(jié)構(gòu)進(jìn)行分離,是行為類(lèi)模式中最復(fù)雜的一種模式箱硕。
訪問(wèn)者(Visitor)模式實(shí)現(xiàn)的關(guān)鍵是如何將作用于元素的操作分離出來(lái)封裝成獨(dú)立的類(lèi)
模板實(shí)現(xiàn)如下:
package com.niuh.designpattern.visitor.v1;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
/**
* <p>
* 訪問(wèn)者模式
* </p>
*/
public class VisitorPattern {
public static void main(String[] args) {
ObjectStructure os = new ObjectStructure();
os.add(new ConcreteElementA());
os.add(new ConcreteElementB());
Visitor visitor = new ConcreteVisitorA();
os.accept(visitor);
System.out.println("=================");
visitor = new ConcreteVisitorB();
os.accept(visitor);
}
}
//抽象訪問(wèn)者
interface Visitor {
void visit(ConcreteElementA element);
void visit(ConcreteElementB element);
}
//具體訪問(wèn)者A類(lèi)
class ConcreteVisitorA implements Visitor {
public void visit(ConcreteElementA element) {
System.out.println("具體訪問(wèn)者A訪問(wèn)-->" + element.operationA());
}
public void visit(ConcreteElementB element) {
System.out.println("具體訪問(wèn)者A訪問(wèn)-->" + element.operationB());
}
}
//具體訪問(wèn)者B類(lèi)
class ConcreteVisitorB implements Visitor {
public void visit(ConcreteElementA element) {
System.out.println("具體訪問(wèn)者B訪問(wèn)-->" + element.operationA());
}
public void visit(ConcreteElementB element) {
System.out.println("具體訪問(wèn)者B訪問(wèn)-->" + element.operationB());
}
}
//抽象元素類(lèi)
interface Element {
void accept(Visitor visitor);
}
//具體元素A類(lèi)
class ConcreteElementA implements Element {
public void accept(Visitor visitor) {
visitor.visit(this);
}
public String operationA() {
return "具體元素A的操作拴竹。";
}
}
//具體元素B類(lèi)
class ConcreteElementB implements Element {
public void accept(Visitor visitor) {
visitor.visit(this);
}
public String operationB() {
return "具體元素B的操作。";
}
}
//對(duì)象結(jié)構(gòu)角色
class ObjectStructure {
private List<Element> list = new ArrayList<Element>();
public void accept(Visitor visitor) {
Iterator<Element> i = list.iterator();
while (i.hasNext()) {
((Element) i.next()).accept(visitor);
}
}
public void add(Element element) {
list.add(element);
}
public void remove(Element element) {
list.remove(element);
}
}
輸出結(jié)果如下:
具體訪問(wèn)者A訪問(wèn)-->具體元素A的操作剧罩。
具體訪問(wèn)者A訪問(wèn)-->具體元素B的操作栓拜。
=================
具體訪問(wèn)者B訪問(wèn)-->具體元素A的操作。
具體訪問(wèn)者B訪問(wèn)-->具體元素B的操作惠昔。
解決的問(wèn)題
穩(wěn)定的數(shù)據(jù)結(jié)構(gòu)和易變的操作耦合問(wèn)題幕与。
需要對(duì)一個(gè)對(duì)象結(jié)構(gòu)中的對(duì)象進(jìn)行很多不同的并且不相關(guān)的操作,而需要避免讓這些操作"污染"這些對(duì)象的類(lèi)镇防,使用訪問(wèn)者模式將這些封裝到類(lèi)中啦鸣。
模式組成
組成(角色) | 作用 |
---|---|
抽象訪問(wèn)者(Visitor)角色 | 定義一個(gè)訪問(wèn)具體元素的接口,為每個(gè)具體元素類(lèi)對(duì)應(yīng)一個(gè)訪問(wèn)操作 visit() 来氧,該操作中的參數(shù)類(lèi)型標(biāo)識(shí)了被訪問(wèn)的具體元素诫给。 |
具體訪問(wèn)者(ConcreteVisitor)角色 | 實(shí)現(xiàn)抽象訪問(wèn)者角色中聲明的各個(gè)訪問(wèn)操作香拉,確定訪問(wèn)者訪問(wèn)一個(gè)元素時(shí)該做什么。 |
抽象元素(Element)角色 | 聲明一個(gè)包含接受操作 accept() 的接口中狂,被接受的訪問(wèn)者對(duì)象作為 accept() 方法的參數(shù)凫碌。 |
具體元素(ConcreteElement)角色 | 實(shí)現(xiàn)抽象元素角色提供的 accept() 操作,其方法體通常都是 visitor.visit(this) 胃榕,另外具體元素中可能還包含本身業(yè)務(wù)邏輯的相關(guān)操作盛险。 |
對(duì)象結(jié)構(gòu)(Object Structure)角色 | 是一個(gè)包含元素角色的容器,提供讓訪問(wèn)者對(duì)象遍歷容器中的所有元素的方法勋又,通常由 List枉层、Set、Map 等聚合類(lèi)實(shí)現(xiàn)赐写。 |
實(shí)例說(shuō)明
實(shí)例概況
用訪問(wèn)者模式實(shí)現(xiàn)一個(gè)用戶訪問(wèn)博客的場(chǎng)景
分析:用戶可以通過(guò)電腦上的Web方式(訪問(wèn)者)或者手機(jī)APP方式(訪問(wèn)者)去訪問(wèn)博客,每篇博客是一個(gè)元素膜赃,然后博客列表是一個(gè)對(duì)象結(jié)構(gòu)類(lèi)挺邀。
使用步驟
步驟1:定義抽象訪問(wèn)者(Visitor)
abstract class Visitor {
public abstract void visitBlog(Element element);
}
步驟2:定義具體訪問(wèn)者(ConcreteVisitor),web 和 app兩種
//具體訪問(wèn)者(ConcreteVisitor)
class WebVisitor extends Visitor {
public void visitBlog(Element element) {
System.out.println("通過(guò)電腦web網(wǎng)站方式訪問(wèn)Blog:" + element.blogName);
}
}
//具體訪問(wèn)者(ConcreteVisitor)
class AppVisitor extends Visitor {
public void visitBlog(Element element) {
System.out.println("通過(guò)手機(jī)App網(wǎng)站方式訪問(wèn)Blog:" + element.blogName);
}
}
步驟3:定義抽象元素(Element)
//抽象元素(Element)
abstract class Element {
public String blogName;
abstract public void accept(Visitor visotr);
}
步驟4:定義具體元素(ConcreteElement)跳座,即博客
//具體元素(ConcreteElement)
class BlogElement extends Element {
public BlogElement(String blogname) {
this.blogName = blogname;
}
public void accept(Visitor visitor) {
visitor.visitBlog(this);
}
}
步驟5:定義對(duì)象結(jié)構(gòu)類(lèi)(ObjectStructure)端铛,即博客列表
//對(duì)象結(jié)構(gòu)類(lèi)(ObjectStructure)
class Blogs {
private List<Element> blogList = new ArrayList<Element>();
public void addBlog(Element element) {
blogList.add(element);
}
public void removeBlog(Element element) {
blogList.remove(element);
}
public void accept(Visitor visitor) {
Iterator<Element> i = blogList.iterator();
while (i.hasNext()) {
((Element) i.next()).accept(visitor);
}
}
}
步驟6:測(cè)試驗(yàn)證
public class VisitorPattern {
public static void main(String[] args) {
Blogs blogs = new Blogs();
blogs.addBlog(new BlogElement("一角錢(qián)技術(shù)第一篇博文"));
blogs.addBlog(new BlogElement("一角錢(qián)技術(shù)第二篇博文"));
blogs.addBlog(new BlogElement("一角錢(qián)技術(shù)第三篇博文"));
blogs.addBlog(new BlogElement("一角錢(qián)技術(shù)第四篇博文"));
Visitor webVisit = new WebVisitor();
Visitor appVisit = new AppVisitor();
blogs.accept(webVisit);
blogs.accept(appVisit);
}
}
輸出結(jié)果
通過(guò)電腦web網(wǎng)站方式訪問(wèn)Blog:一角錢(qián)技術(shù)第一篇博文
通過(guò)電腦web網(wǎng)站方式訪問(wèn)Blog:一角錢(qián)技術(shù)第二篇博文
通過(guò)電腦web網(wǎng)站方式訪問(wèn)Blog:一角錢(qián)技術(shù)第三篇博文
通過(guò)電腦web網(wǎng)站方式訪問(wèn)Blog:一角錢(qián)技術(shù)第四篇博文
通過(guò)手機(jī)App網(wǎng)站方式訪問(wèn)Blog:一角錢(qián)技術(shù)第一篇博文
通過(guò)手機(jī)App網(wǎng)站方式訪問(wèn)Blog:一角錢(qián)技術(shù)第二篇博文
通過(guò)手機(jī)App網(wǎng)站方式訪問(wèn)Blog:一角錢(qián)技術(shù)第三篇博文
通過(guò)手機(jī)App網(wǎng)站方式訪問(wèn)Blog:一角錢(qián)技術(shù)第四篇博文
優(yōu)點(diǎn)
訪問(wèn)者(Visitor)模式是一種對(duì)象行為型模式,其主要優(yōu)點(diǎn)如下疲眷。
- 擴(kuò)展性好禾蚕。能夠在不修改對(duì)象結(jié)構(gòu)中的元素的情況下,為對(duì)象結(jié)構(gòu)中的元素添加新的功能狂丝。
- 復(fù)用性好换淆。可以通過(guò)訪問(wèn)者來(lái)定義整個(gè)對(duì)象結(jié)構(gòu)通用的功能几颜,從而提高系統(tǒng)的復(fù)用程度倍试。
- 靈活性好。訪問(wèn)者模式將數(shù)據(jù)結(jié)構(gòu)與作用于結(jié)構(gòu)上的操作解耦蛋哭,使得操作集合可相對(duì)自由地演化而不影響系統(tǒng)的數(shù)據(jù)結(jié)構(gòu)县习。
- 符合單一職責(zé)原則。訪問(wèn)者模式把相關(guān)的行為封裝在一起谆趾,構(gòu)成一個(gè)訪問(wèn)者躁愿,使每一個(gè)訪問(wèn)者的功能都比較單一。
缺點(diǎn)
訪問(wèn)者(Visitor)模式的主要缺點(diǎn)如下沪蓬。
- 增加新的元素類(lèi)很困難彤钟。在訪問(wèn)者模式中,每增加一個(gè)新的元素類(lèi)怜跑,都要在每一個(gè)具體訪問(wèn)者類(lèi)中增加相應(yīng)的具體操作样勃,這違背了“開(kāi)閉原則”吠勘。
- 破壞封裝。訪問(wèn)者模式中具體元素對(duì)訪問(wèn)者公布細(xì)節(jié)峡眶,這破壞了對(duì)象的封裝性剧防。
- 違反了依賴倒置原則。訪問(wèn)者模式依賴了具體類(lèi)辫樱,而沒(méi)有依賴抽象類(lèi)峭拘。
應(yīng)用場(chǎng)景
通常在以下情況可以考慮使用訪問(wèn)者(Visitor)模式。
- 對(duì)象結(jié)構(gòu)相對(duì)穩(wěn)定狮暑,但其操作算法經(jīng)常變化的程序鸡挠。
- 對(duì)象結(jié)構(gòu)中的對(duì)象需要提供多種不同且不相關(guān)的操作,而且要避免讓這些操作的變化影響對(duì)象的結(jié)構(gòu)搬男。
- 對(duì)象結(jié)構(gòu)包含很多類(lèi)型的對(duì)象拣展,希望對(duì)這些對(duì)象實(shí)施一些依賴于其具體類(lèi)型的操作。
模式的擴(kuò)展
訪問(wèn)者(Visitor)模式是使用頻率較高的一種設(shè)計(jì)模式缔逛,它常常同以下兩種設(shè)計(jì)模式聯(lián)用备埃。
(1) 與“迭代器模式”聯(lián)用。因?yàn)樵L問(wèn)者模式中的“對(duì)象結(jié)構(gòu)”是一個(gè)包含元素角色的容器褐奴,當(dāng)訪問(wèn)者遍歷容器中的所有元素時(shí)按脚,常常要用迭代器。如案例中的對(duì)象結(jié)構(gòu)是用 List 實(shí)現(xiàn)的敦冬,它通過(guò) List 對(duì)象的 Itemtor() 方法獲取迭代器辅搬。如果對(duì)象結(jié)構(gòu)中的聚合類(lèi)沒(méi)有提供迭代器,也可以用迭代器模式自定義一個(gè)脖旱。
(2) 訪問(wèn)者(Visitor)模式同“組合模式”聯(lián)用堪遂。因?yàn)樵L問(wèn)者(Visitor)模式中的“元素對(duì)象”可能是葉子對(duì)象或者是容器對(duì)象,如果元素對(duì)象包含容器對(duì)象萌庆,就必須用到組合模式蚤氏,其結(jié)構(gòu)圖如下:
源碼中的應(yīng)用
javax.lang.model.element.Element
javax.lang.model.element.ElementVisitor
javax.lang.model.type.TypeMirror
javax.lang.model.type.TypeVisitor
PS:以上代碼提交在 Github :https://github.com/Niuh-Study/niuh-designpatterns.git
文章持續(xù)更新,可以公眾號(hào)搜一搜「 一角錢(qián)技術(shù) 」第一時(shí)間閱讀踊兜, 本文 GitHub org_hejianhui/JavaStudy 已經(jīng)收錄竿滨,歡迎 Star。