天天看點

通路者模式

通路者模式是一種将資料操作和資料結構分離的設計模式,是23種設計模式中非常複雜的一種,而且使用頻率并不高。

定義:封裝一些作用于某種資料結構中的各元素的操作(通路),可以在不改變這個資料的前提下定義作用于這些元素的新操作。

顧名思義,某些不能改變的資料,對于不同的通路者有不同的通路(或者操作),為不同的通路者提供相對應的操作。例如:公司CEO就能看到公司所有的真實财報資料,而作為一個員工可能就隻能知道同比去年的增長比例。

通路者模式

Visitor:通路者抽象類(或者接口),它定義了對每一個元素(Element)通路的行為,它的參數就是可以通路的元素;理論上,它的方法個數與元素個數是一樣的,是以,通路者模式要求元素的類族要穩定,不能頻繁的添加、移除元素。如果出現頻繁修改Visitor接口的情況,說明可能并不适合使用通路者模式。

ConcreteVisitor:具體的通路者,需要實作每一個元素類通路時所産生的具體行為。

Element:元素接口(或抽象類),它定義了一個接收通路者的方法(<code>accept()</code>方法),意義在于每一個元素都要刻意被通路者通路。

ElementA、ElementB:具體的元素類,提供接受通路方法的具體實作,而這個具體的實作,通常情況下是使用通路者提供的通路該元素類的方法。

ObjectStructure:定義當中所提到的對象結構,對象結構是一個抽象表述,它内部管理了元素集合,并且可以疊代這些元素共通路者通路。

通路者模式的最大優點就是增加通路者非常容易,新建立一個實作了<code>Visitor</code>接口的類,然後實作兩個<code>visit()</code>方法對不同的元素進行不同的操作,進而達到資料與資料操作分離的目的。如果不實用通路者模式,必定需要使用<code>if-else</code>和類型轉換,這便是代碼的維護難度更新了。由此可以看出通路者模式的作用。

PS:通路者模式違反了迪米特原則(對通路者公布元素細節)以及依賴倒置原則(依賴了具體類,沒有依賴抽象),由此可見,此模式需要應用在特定的情況中。

這裡就以公司為例,公司員工暫且分為開發人員和營運人員,而公司的CEO和CTO對于不同員工的KPI關注點不同,是以我們需要做出不同的處理,接着看看代碼實作。

很簡單,名字初始化和一個抽象的<code>accept()</code>方法。

具體的員工,根據各自不同的職責添加了不同的方法,開發人員的KPI和代碼産量相關,于是添加了擷取代碼行數的方法,而營運人員的KPI和新增使用者量相關,于是添加了擷取新增使用者數的方法。

接下來看看通路者類的定義

這裡可以看到,直接從方法上就區分<code>Developer</code>和<code>Operator</code>,這裡主要考慮到的是,如果使用基類<code>Staff</code>作為參數的話代碼就會是這個樣子

可以看到,在<code>visit()</code>方法中,我們就需要判斷參數的類型以及類型強制轉換,這樣的代碼難以擴充和維護。

這是通路者模式的一個優點,也是一個缺點,優點在于代碼清晰,某種程度上代碼的維護和擴張更好;而缺點也是一樣,如果需要添加一類<code>Staff</code>,所有的<code>Visitor</code>都需要在實作一個新的<code>visit()</code>方法。

接下來是具體的通路者代碼,這裡設定CTO更加關注開發人員,CEO更加關注營運人員。

這裡的對象結構,直接就設定成了公司,集合就是員工們

用戶端代碼

具體輸出如下:

在Java代碼中很常見的一種寫法,聲明父類對象建立子類對象;聲明是<code>List</code>類型(也就是靜态類型即明顯類型),建立的是<code>ArrayList</code>的對象(實際類型)。

這裡就需要提到一個詞,分派(Dispatch)。當使用上述形式聲明并建立對象,根據對象的類型對方法進行選擇,這就是分派,而分派有可以分為靜态分派(Static Dispatch)和動态分派(Dynamic Dispatch)。

靜态分派,對應的就是編譯時,根據靜态類型資訊發生的分派。方法重載就屬于靜态分派

動态分派,對應的就是運作時,動态地置換掉某個方法。方法重寫就屬于動态分派

簡化三個類之間的關系

執行類,execute()方法有三個重載方法,方法的參數分别上面對應的三個類型<code>Staff</code>、<code>Developer</code>、<code>Operator</code>的對象。

測試代碼以及測試結果

可以推斷出,傳入三個對象,最後執行的方法都是參數類型是<code>Staff</code>的方法,即使三個對象有不同的真實類型

方法重載中實際起作用的是它們靜态類型,也就是在編譯時期就完成了分派,即靜态分派。

三個類自帶<code>execute()</code>方法,<code>Developer</code>和<code>Operator</code>繼承<code>Staff</code>,并重寫了<code>execute()</code>方法

測試代碼以及結果

測試時的情況相同,三個對象,其靜态類型都是<code>Staff</code>,而實際類型分别是<code>Staff</code>、<code>Developer</code>和<code>Operator</code>。可以看到重寫<code>execute()</code>方法都生效了,各自輸出了對應的内容。

Java編譯器在編譯時期并不總是知道哪些代碼會被執行,因為編譯器僅僅知道對象的靜态類型,而不知道對象的真實類型;而方法的調用則是根據對象的真實類型,而不是靜态類型。

首先需要了解一個叫宗量的概念。一個方法所屬的對象叫做方法的接收者,方法的接收者與方法的參量統稱做方法的宗量。而根據分派可以基于多少種宗量,可以将面向對象的語言劃分為單分派語言和多分派語言。

單分派語言根據一個宗量的類型(真實類型)進行對方法的選擇

多分派語言根據多個的宗量的類型對方法進行選擇

那<code>Java</code>屬于什麼類型呢?

我們可以分析一下,<code>Java</code>中靜态分派時決定方法的選擇的宗量包括方法的接收者和方法參數的靜态類型,是以是多分派;而在動态分派時,方法的選擇隻會考慮方法的接收者的實際類型,是以是單分派。其實<code>Java</code>語言是支援靜态多分派和動态單分派的語言。

那雙重分派又是什麼呢?分派和通路者模式又有什麼關系呢?接下來就會解釋這些問題

<code>Java</code>支援靜态多分派和動态單分派,并不支援動态多分派;于是就有了兩次單分派組成的雙重分派來替代動态多分派。而通路者模式正好就用到了雙重分派的技術。

雙重分派技術就是在選擇一個方法的時候,不僅僅要根據方法的接收者的運作時差別,還要根據參數的運作時差別(這樣達到兩次分派的效果)。

在通路者模式中,用戶端将具體的對象傳遞給通路者,也就是<code>staff.accept(visitor);</code>方法的調用,完成第一次分派;然後具體的通路者作為參數傳入到具體的對象的方法中,也就是這句代碼<code>visitor.visit(this);</code>,将<code>this</code>作為參數傳遞進去完成第二次分派。雙分派意味着得到的執行操作決定于請求的種類和接受者的類型。雙重分派的核心就是<code>this</code>對象。

從通路者模式可以看出,雙重分派就是在沖在方法委派的前面加上了繼承的重寫,使得從某種角度來說重載變成了動态。

相信注解應該不會陌生,現在很多出名架構的使用方式都是使用注解,例如:<code>ButterKnife</code>、<code>Dagger</code>、<code>Retrofit</code>等等,都是以注解的方式使用,已達到簡化代碼或者降低耦合度的目的。而注解又可以分為運作時注解和編譯時注解,運作時注解由于性能問題也一直被人诟病,編譯時注解的核心原理依賴APT(Annotation Processing Tools)實作,之前提到的架構也是基于APT實作的。

而對于注解的解析過程就是遵從通路者模式的,其元素就是包、類、方法、方法參數等(其實就是可以被添加注解那些元素),對于元素的通路者支援所有的元素通路,通過繼承一個抽象的元素通路者實作針對不同類型進行不同的處理。

注解相關具體的内容我不是很了解,隻是簡單的說明一下。

通路者模式把資料結構和作用于結構上的操作解耦合,使得操作集合可相對自由地演化。通路者模式适用于資料結構相對穩定算法又易變化的系統。因為通路者模式使得算法操作增加變得容易。若系統資料結構對象易于變化,經常有新的資料對象增加進來,則不适合使用通路者模式。

優點

擴充性好: 在不修改對象結構中的元素的情況下,為對象結構中的元素添加新的功能。

複用性好: 通過通路者來定義整個對象結構通用的功能,進而提高複用程度。

分離無關行為: 通過通路者來分離無關的行為,把相關的行為封裝在一起,構成一個通路者,這樣每一個通路者的功能都比較單一。

缺點

對象結構變化很困難: 不适用于對象結構中的類經常變化的情況,因為對象結構發生了改變,通路者的接口和通路者的實作都要發生相應的改變,代價太高。

破壞封裝: 通路者模式通常需要對象結構開放内部資料給通路者和<code>ObjectStructrue</code>,這破壞了對象的封裝性。

___________________________________________________

作者:MrTrying

連結:https://www.jianshu.com/p/a2fdb3387301

來源:簡書

簡書著作權歸作者所有,任何形式的轉載都請聯系作者獲得授權并注明出處。

繼續閱讀