一、前言
有時候不想動腦子,就懶得看源碼又不像浪費時間所以會看看書,但是又記不住,所以決定開始寫"抄書"系列。本系列大部分內(nèi)容都是來源于《 圖解設(shè)計模式》(【日】結(jié)城浩 著)。該系列文章可隨意轉(zhuǎn)載。
二、Visitor 模式
Visitor 模式:訪問數(shù)據(jù)結(jié)構(gòu)并處理數(shù)據(jù)
1. 介紹
在 Visitor 模式中,數(shù)據(jù)結(jié)構(gòu)和數(shù)據(jù)被分離開來。我們需要編寫一個“訪問者”的類來訪問數(shù)據(jù)結(jié)構(gòu)中的元素,并把對各元素的處理交給訪問者類。這樣當需要增加新的處理時,我們只需要編寫新的訪問者,然后讓數(shù)據(jù)結(jié)構(gòu)可以接受訪問者的訪問即可。
Visitor 模式 中出場的角色:
- Visitor(訪問者):該角色負責對數(shù)據(jù)結(jié)構(gòu)中每個具體的元素(ConcreteElement角色)聲明一個用于訪問XXX 的 visit(XXX) 方法。visit(XXX) 是用于處理 XXX 的方法,負責實現(xiàn)該方法的是 ConcreteVisitor 橘色。
- ConcreteVisitor(具體的訪問者):ConcreteVisitor 角色負責實現(xiàn) Visitor 角色所定義的接口。他要實現(xiàn)所有的 visit(XXX) 方法,即實現(xiàn)如何處理每個 ConcreteElement 角色。
- Element(元素):Element 角色表示Visitor 角色的訪問對象。他聲明了接受訪問者的 accept 方法。accept 方法接收到的參數(shù)是 Visitor 角色。
- ConcreteElement :ConcreteElement 角色負責實現(xiàn) Element 角色所定義的接口。
- ObjectStructure(對象結(jié)構(gòu)):ObjectStructure 角色負責處理 Element 角色的集合。ConcreteVisitor 角色為每個 Element 角色都準備了處理方法。
類圖如下:
Demo如下:使用 ListVisitor 來訪問 rootDir 目錄下的資源
public interface Element {
void accept(Visitor visitor);
}
public interface Entry extends Element {
/**
* 獲取文件名
*
* @return
*/
String getName();
/**
* 獲取文件大小
*
* @return
*/
int getSize();
/**
* 添加目錄
*
* @param entry
* @return
*/
default Entry addEntry(Entry entry) {
throw new RuntimeException();
}
/**
* 生成迭代器
* @return
*/
default Iterator<Entry> iterator() {
throw new RuntimeException();
}
/**
* 輸出路徑
* @return
*/
default String thisPath() {
return getName() + "(" + getSize() + ")";
}
/**
* 暴露出方法供訪問者訪問
* @param visitor
*/
default void accept(Visitor visitor) {
visitor.visit(this);
}
}
// 訪問者接口
public interface Visitor {
/**
* 訪問
* @param entry
*/
void visit(Entry entry);
}
// 文件
public class File implements Entry {
private String name;
private int size;
public File(String name, int size) {
this.name = name;
this.size = size;
}
@Override
public String getName() {
return name;
}
@Override
public int getSize() {
return size;
}
}
// 文件夾
public class Directory implements Entry {
private String name;
private List<Entry> entries = Lists.newArrayList();
public Directory(String name) {
this.name = name;
}
@Override
public String getName() {
return name;
}
@Override
public int getSize() {
return entries.stream()
.mapToInt(Entry::getSize)
.sum();
}
@Override
public Entry addEntry(Entry entry) {
entries.add(entry);
return this;
}
@Override
public Iterator<Entry> iterator() {
return entries.iterator();
}
}
public class ListVisitor implements Visitor {
/**
* 當前目錄
*/
private String currentDir = "";
@Override
public void visit(Entry entry) {
System.out.println(currentDir + "/" + entry.thisPath());
if (entry instanceof Directory) {
String saveDir = currentDir;
currentDir = currentDir + "/" + entry.getName();
entry.iterator()
.forEachRemaining(sonEntry ->
sonEntry.accept(this));
currentDir = saveDir;
}
}
}
public class VisitorDemoMain {
public static void main(String[] args) {
Entry rootDir = new Directory("root");
Entry binDir = new Directory("bin");
Entry tmpDir = new Directory("tmp");
Entry usrDir = new Directory("usr");
Entry hanakoDir = new Directory("hanako");
usrDir.addEntry(hanakoDir);
rootDir.addEntry(binDir);
rootDir.addEntry(tmpDir);
rootDir.addEntry(usrDir);
hanakoDir.addEntry(new File("memo.tex", 10));
binDir.addEntry(new File("vi", 1000));
binDir.addEntry(new File("latex", 2000));
// ListVisitor 訪問者訪問rootDir 目錄下的文件和文件夾
rootDir.accept(new ListVisitor());
}
}
輸出如下:
2. 應(yīng)用
印象中在哪看過,但是一時想不起來了。想起來再補
個人使用:該部分內(nèi)容是寫給自己看的,幫助自身理解,因此就不交代項目背景了,讀者請自行忽略(???):
-
項目 A 中,某一數(shù)據(jù)內(nèi)容固定,但是存在多處地方會獲取該部分數(shù)據(jù),可以通過訪問者模式來控制不同的訪問者來獲取不同部分的數(shù)據(jù)。
// 書的數(shù)據(jù) @Data public class BookData { /** * 作者 */ private String author; /** * 表體 */ private String title; /** * 內(nèi)容 */ private String content; /** * 接受訪問者訪問 * * @param bookVisitor */ public void accept(BookVisitor bookVisitor) { if (bookVisitor instanceof TitleBookVisitor) { bookVisitor.visit(title); } else if (bookVisitor instanceof AuthorBookVisitor) { bookVisitor.visit(author); } else if (bookVisitor instanceof ContentBookVisitor) { bookVisitor.visit(content); } else { throw new RuntimeException("未知的訪問者"); } } } // 書籍訪問者 public interface BookVisitor { /** * 訪問數(shù)據(jù) * @param data */ void visit(String data); } // 作者訪問 public class AuthorBookVisitor implements BookVisitor { @Override public void visit(String data) { System.out.println("author = " + data); } } // 標題訪問 public class TitleBookVisitor implements BookVisitor { @Override public void visit(String data) { System.out.println("title = " + data); } } // 內(nèi)容訪問 public class ContentBookVisitor implements BookVisitor { @Override public void visit(String data) { System.out.println("content = " + data); } } public class DemoMain { public static void main(String[] args) { final BookData bookData = new BookData(); bookData.setAuthor("夏義春"); bookData.setTitle("夏義春的一生"); bookData.setContent("夏義春一生天下第一"); bookData.accept(new AuthorBookVisitor()); bookData.accept(new TitleBookVisitor()); bookData.accept(new ContentBookVisitor()); } }
輸入內(nèi)容
3. 總結(jié)
數(shù)據(jù)結(jié)構(gòu)是單一一個接口或?qū)嵗?,而?shù)據(jù)是一個實例,當需要訪問數(shù)據(jù)時,將 數(shù)據(jù)結(jié)構(gòu) API 傳給數(shù)據(jù)就可以,這樣的話對后面切換數(shù)據(jù)結(jié)構(gòu)訪問非常容易。當需要增加新的處理時,只需要編寫新的訪問者,然后讓數(shù)據(jù)結(jié)構(gòu)可以接受訪問者訪問即可。但是相對的,對于數(shù)據(jù)結(jié)構(gòu)的設(shè)計需要具有一定前瞻性。
通常在以下情況可以考慮使用訪問者(Visitor)模式:
- 對象結(jié)構(gòu)相對穩(wěn)定,但其操作算法經(jīng)常變化的程序。
- 對象結(jié)構(gòu)中的對象需要提供多種不同且不相關(guān)的操作,而且要避免讓這些操作的變化影響對象的結(jié)構(gòu)。
- 對象結(jié)構(gòu)包含很多類型的對象,希望對這些對象實施一些依賴于其具體類型的操作。
擴展思路:
-
雙重分發(fā):
我們來整理下 Visitor 模式中方法的調(diào)用關(guān)系
accept(接受)方法的調(diào)用方式如下:element.accept(visitor);
visit(訪問)方法的調(diào)用方式如下:
visitor.visit(elemnt)
對比一下就會發(fā)現(xiàn),上面兩個方法是相反的關(guān)系。element 接受 visitor, 而 visitor 又訪問 element。在Visitor 模式中 ConcreteElement 和 ConcreteVisitor 這兩個角色共同決定了實際進行的處理。這種消息分發(fā)的方式一般被稱為雙重分發(fā)。
-
為什么如此復(fù)雜:
Visitor 把簡單的問題復(fù)雜化了嗎?如果需要循環(huán)處理,在數(shù)據(jù)結(jié)構(gòu)的類中直接編寫循環(huán)語句不就解決了嗎?為什么要搞出 accept 方法 和 visit 方法之間那樣復(fù)雜的調(diào)用關(guān)系呢?Visitor 模式的目的是將處理從數(shù)據(jù)結(jié)果中分離出來。數(shù)據(jù)結(jié)構(gòu)很重要,它能將元素集合和關(guān)聯(lián)在一起,但是需要注意的是,保存數(shù)據(jù)結(jié)構(gòu)與以數(shù)據(jù)結(jié)構(gòu)為基礎(chǔ)進行處理是兩個不同的東西。
在示例程序中我們創(chuàng)建了 ListVisitor 類作為顯示文件夾內(nèi)容的 ConcreteVisitor 角色。通常,ConcreteVisitor 角色的開發(fā)可以獨立于 File 類 和 Directory 類,也就是說 Visitor 模式提高了File類和 Directory 類作為組件的獨立性。如果將進行處理的方法定義在 File 類和 Directory 類作為組件的獨立性。如果將進行處理的方法定義在 File類和 Directory 類中,當每次要擴展功能,增加新的處理時就不得不去修改 File 類和 Directory類。
-
易于增加 ConcreteVisitor 角色
使用 Visitor 模式可以很容易地修改 ConcreteVisitor 角色,因為具體的處理被交給 ConcreteVisitor角色負責,因此完全不用修改 ConcreteElement 角色。 -
難以增加 ConcreteElement 角色
雖然使用 Visitor 模式可以很容易地增加 ConcreteVisitor 角色,不過他卻很難應(yīng)對 ConcreteElement 角色的增加。 -
Visitor 工作所需的條件 : Element 角色必須向 Visitor 角色公開足夠多的信息。不過也可以基于此限制訪問者能獲取到的數(shù)據(jù)
相關(guān)設(shè)計模式:
- Iterator 模式 :Iterator 模式 和 Visitor 模式都是在某種數(shù)據(jù)結(jié)構(gòu)上進行處理。Iterator 模式用于逐個遍歷保存在數(shù)據(jù)結(jié)構(gòu)中的元素。Visitor 模式用于對保存在數(shù)據(jù)結(jié)構(gòu)中的元素進行某種特定的處理。
- Composite 模式 :有時訪問者所訪問的數(shù)據(jù)結(jié)構(gòu)會使用 Composite 模式
- Interpret 模式 : 在 Interpret 模式匯總,有時會使用 Visitor 模式。例如在生成了語法樹后可能會使用 Visitor 模式訪問語法樹的各個節(jié)點進行處理。
一時的小想法,僅僅個人理解,無需在意 :文章來源:http://www.zghlxwxcb.cn/news/detail-793295.html
- 訪問者模式的最主要的作用是將數(shù)據(jù)和處理二者分離開,對于同一數(shù)據(jù)可以存在多種處理結(jié)果。而如果是為了完成這個目標有很多種其他模式可以選擇(如策略模式、處理器模式等),而其中的關(guān)系就是主動和被動的區(qū)別,策略模式時,數(shù)據(jù)是被動的,只能被對應(yīng)策略獲取;而訪問者模式下,數(shù)據(jù)則是主動的,由數(shù)據(jù)來確定暴露什么內(nèi)容給訪問者。
- 訪問者模式還可以通過Element 來控制 Visitor 的數(shù)據(jù)訪問權(quán)限,通過暴露出的數(shù)據(jù)不同來控制 Visitor 獲取的數(shù)據(jù)內(nèi)容的范圍。如上面的例子中 BookData 對于所有的訪問者 BookVisitor,可以自由控制暴露的內(nèi)容,甚至是拋出異常。
在權(quán)限控制的場景是否適用?如每個角色都是一個訪問者,去訪問目錄內(nèi)容,根據(jù)角色不同會給于不同的可訪問的內(nèi)容?
三、Chain of Responsibility 模式
Chain of Responsibility 模式 : 推卸責任
1. 介紹
當外部請求程序進行某個處理,但程序暫時無法直接決定由哪個對象負責處理時,就需要推卸責任,在這種情況下,我們可以考慮將多個對象組成一條職責鏈。然后按照他們在職責鏈上的順序一個一個地找出到底應(yīng)該誰負來負責處理。這種模式稱之為 Chain of Responsibility 模式。
Chain of Responsibility模式 中出場的角色:
- handler (處理者) :Handler 角色定義了處理請求的接口(API),Handler 角色知道下一個處理者是誰,如果自己無法處理請求,會將請求轉(zhuǎn)發(fā)給下一個處理者,下一個處理著也是 Handler角色。
- ConcreteHandler(具體的處理者):ConcreteHandler角色是處理請求的具體角色。
- Client(請求者):該角色是向第一個 ConcreteHandler角色發(fā)送請求的角色。
類圖如下:
Demo 如下:可以看到不同的問題會交由不同的 Support 來解決,當一個 Support 不能解決時會交由 next 來處理
public abstract class Support {
/**
* 下一個解決器
*/
private Support next;
/**
* 設(shè)置下一個解決器
* @param next
* @return
*/
public Support setNext(Support next) {
this.next = next;
return this;
}
/**
* 直接解決問題
* @param trouble
*/
public final void support(String trouble) {
if (resolve(trouble)) {
done(trouble);
} else if (next != null) {
next.support(trouble);
} else {
fail(trouble);
}
}
/**
* 解決問題
*
* @param trouble
* @return
*/
protected abstract boolean resolve(String trouble);
/**
* 問題已解決
*
* @param trouble
*/
protected void done(String trouble) {
System.out.println(trouble + " 問題已被 " + this.getClass().getSimpleName() + " 解決");
}
/**
* 問題未解決
*
* @param trouble
*/
protected void fail(String trouble) {
System.out.println(trouble + " 問題未解決");
}
}
// 解決特定編號的問題
public class SpecialSupport extends Support {
@Override
protected boolean resolve(String trouble) {
return "夏義春".equals(trouble);
}
}
// 解決小于指定長度的問題
public class LimitSupport extends Support {
@Override
protected boolean resolve(String trouble) {
return StringUtils.length(trouble) < 10;
}
}
// 不解決任何問題
public class NoSupport extends Support {
@Override
protected boolean resolve(String trouble) {
return false;
}
}
public class ChainDemoMain {
public static void main(String[] args) {
Support noSupport = new NoSupport();
Support limitSupport = new LimitSupport();
Support specialSupport = new SpecialSupport();
// 構(gòu)建責任鏈
specialSupport.setNext(limitSupport.setNext(noSupport));
specialSupport.support("你好");
specialSupport.support("夏義春");
specialSupport.support("1234567890");
}
}
輸出如下:
2. 應(yīng)用
-
責任鏈模式在很多地方都有所使用,如 Spring中的過濾器、攔截器、Dubbo中的 Filter, Spring Cloud Gateway 中的 GlobalFilter 等等。這里以Spring的 攔截器為例。HandlerInterceptorAdapter 定義如下,
public abstract class HandlerInterceptorAdapter implements HandlerInterceptor { /** * This implementation always returns <code>true</code>. */ public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { return true; } /** * This implementation is empty. */ public void postHandle( HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { } /** * This implementation is empty. */ public void afterCompletion( HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { } }
在 DispatcherServlet#doDispatch 中,Spring 在分發(fā)請求的前后都會將請求交由 HandlerInterceptor 的責任鏈來執(zhí)行一遍,具體的代碼在 HandlerExecutionChain 中,如下是其中一小部分:
... boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception { // 遍歷所有注冊的攔截器并依次觸發(fā) for (int i = 0; i < this.interceptorList.size(); i++) { HandlerInterceptor interceptor = this.interceptorList.get(i); if (!interceptor.preHandle(request, response, this.handler)) { triggerAfterCompletion(request, response, null); return false; } this.interceptorIndex = i; } return true; } void applyPostHandle(HttpServletRequest request, HttpServletResponse response, @Nullable ModelAndView mv) throws Exception { for (int i = this.interceptorList.size() - 1; i >= 0; i--) { HandlerInterceptor interceptor = this.interceptorList.get(i); interceptor.postHandle(request, response, this.handler, mv); } } ...
個人使用:該部分內(nèi)容是寫給自己看的,幫助自身理解,因此就不交代項目背景了,讀者請自行忽略(???):
-
在項目A 中,需要對客戶進行計費,客戶可以自定義計費規(guī)則組合,因此對于每種計費規(guī)則都是一個實現(xiàn)類,這里實現(xiàn)則是將每個多個客戶的計費規(guī)則組成成一個鏈路,即責任鏈。和上面的區(qū)別在于,上面的場景當某一個 Support 處理完成后就不再執(zhí)行下面的Support,而這里的情況則是需要將鏈路中所有可以處理當前情況的Support 全部執(zhí)行,得到最終結(jié)果。Demo如下:
public abstract class Rule { /** * 下一個規(guī)則 */ @Setter private Rule next; /** * 處理費用 * * @param fee */ public final String handle(String fee) { fee = doHandle(fee); if (next != null) { return next.handle(fee); } return fee; } /** * 處理 * * @param fee */ protected abstract String doHandle(String fee); } public class VipRule extends Rule { // 處理 VIP 邏輯 @Override protected String doHandle(String fee) { return fee.contains("vip") ? fee + "vip nb" : fee; } } public class SpecialRule extends Rule { // 客戶定制化邏輯 @Override protected String doHandle(String fee) { return fee.length() > 5 ? fee + " 長度大于5" : fee + " 長度小于5"; } } public class DefaultRule extends Rule { @Override protected String doHandle(String fee) { return fee; } } public class DemoMain { public static void main(String[] args) { final Rule vipRule = new VipRule(); final Rule specialRule = new SpecialRule(); final Rule defaultRule = new DefaultRule(); vipRule.setNext(specialRule); specialRule.setNext(defaultRule); System.out.println(vipRule.handle("vip 001")); System.out.println("----------------"); System.out.println(vipRule.handle("夏義春 001")); } }
輸出如下:
-
在項目B中,每個客戶可以定制各自的單證處理邏輯,多個邏輯可以組成,形成鏈路后,依次執(zhí)行邏輯,基本同項目 A 的情況,這里不再贅述。
3. 總結(jié)
擴展思路
- 弱化了發(fā)起請求方和處理請求的人之間的關(guān)系 :即弱化了發(fā)出請求的人(Client)與處理請求的人(ConcreteHandler)的之間的關(guān)系,當 Client 向第一個 ConcreteHandler 發(fā)出請求后,請求會在責任鏈中傳播,最終處理請求的可能并不是第一個 ConcreteHandler,而是責任鏈中的某一個或者幾個 Handler。
- 可以動態(tài)改變職責鏈 :我們可以很輕易的增加或減少責任鏈中的邏輯。
- 專注于自己的工作 :責任鏈模式可以讓每個 Handler 專注自身的邏輯,而無需考慮一些額外的場景邏輯。
相關(guān)設(shè)計模式
- Composite 模式:Handler 角色經(jīng)常會使用 Composite 模式
- Command 模式: 有時候會使用 Command 模式向 Handler 角色發(fā)送請求
一時的小想法,僅僅個人理解,無需在意 :
- 責任鏈模式有兩種情況,一種是執(zhí)行即結(jié)束,即鏈路中的某個 Handler 可以處理這個請求后則阻斷后續(xù)鏈路,另一種是責任鏈中所有支持當前請求的Handler都會將結(jié)果處理,并返回最終結(jié)果。兩種情況需要考慮自己的業(yè)務(wù)場景去選擇。
參考內(nèi)容
https://www.cnblogs.com/yb-ken/p/15084837.html文章來源地址http://www.zghlxwxcb.cn/news/detail-793295.html
到了這里,關(guān)于設(shè)計模式⑥ :訪問數(shù)據(jù)結(jié)構(gòu)的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!