重新訪問了訪客模式

訪客模式是面向對象設計中最被高估但又被低估的模式之一。 高估了它,因為它常常被選擇得太快( 可能是由建筑宇航員選擇的 ),然后以錯誤的方式添加時會膨脹本來非常簡單的設計。 如果您不遵循教科書示例,那么它可能會非常強大,因此被低估了。 讓我們詳細看一下。

問題1:命名

它的最大缺陷(在我看來)是命名本身。 “訪客”模式。 當我們用google搜索它時,我們很可能會在相關的Wikipedia文章中找到自己,顯示類似這樣的有趣圖像:

維基百科訪客模式示例

對。 對于我們98%的人在日常軟件工程工作中對車輪,發動機和車身的思考而言,這很明顯,因為我們知道,機修工向我們收取幾千美元的汽車維修費用后,我們會首先訪問車輪,然后是發動機,然后最終訪問我們的錢包并接受我們的現金。 如果我們很不幸,他也會在我們工作時拜訪我們的妻子,但她永遠不會接受那個忠實的靈魂。

但是,解決工作中其他問題的2%的人呢? 就像我們為電子銀行系統,證券交易所客戶,Intranet門戶等編寫復雜的數據結構時一樣。為什么不將訪客模式應用于真正的分層數據結構? 喜歡文件夾和文件? (好的,畢竟不是那么復雜)

好的,所以我們將“訪問”文件夾,每個文件夾將讓其文件“接受”為“訪客”,然后我們也讓訪問者“訪問”這些文件。 什么?? 汽車讓其零件接納訪客,然后讓訪客自我訪問嗎? 這些條款具有誤導性。 它們是通用的,適合設計模式。 但是它們會殺死您的現實設計,因為沒有人會考慮“接受”和“訪問”,而實際上您是在讀/寫/刪除/修改文件系統。

問題2:多態

當應用于錯誤的情況時,這比命名引起的頭痛甚至更多。 訪客為什么在地球上認識其他所有人? 為什么訪問者需要針對層次結構中每個涉及元素的方法? 多態性和封裝要求將實現隱藏在API的后面。 (我們數據結構的)API可能以某種方式實現了復合模式 ,即其部分繼承自公共接口。 好吧,當然,車輪不是汽車,我妻子也不是機械師。 但是當我們采用文件夾/文件結構時,它們不是全部都是java.util.File對象嗎?

了解問題

實際的問題不是訪問代碼的命名和可怕的API詳細程度,而是對模式的誤解。 這不是最適合訪問帶有許多不同類型對象的大型復雜數據結構的模式。 這種模式最適合于訪問幾種不同類型的簡單數據結構,但要訪問數百名訪問者。 取文件和文件夾。 那是一個簡單的數據結構。 您有兩種類型。 一個可以包含另一個,兩者共享一些屬性。 各種訪客可能是:

  • CalculateSizeVisitor
  • FindOldestFileVisitor
  • DeleteAllVisitor
  • FindFilesByContentVisitor
  • ScanForVirusesVisitor
  • …你給它起名字

我仍然不喜歡命名,但是這種模式在這種范例中可以完美地工作。

那么,訪客模式何時“錯誤”?

我想以jOOQ QueryPart結構為例。 其中有很多,可以對各種SQL查詢結構進行建模,從而使jOOQ可以構建和執行任意復雜度的SQL查詢。 讓我們舉幾個例子:

  • 健康)狀況
    • 組合條件
  • 領域
    • 表格欄位
  • 字段清單

還有更多。 它們中的每一個都必須能夠執行兩個操作:渲染SQL和綁定變量。 那將使兩個訪問者每個人都知道……40-50種類型……? 也許在遙遠的將來,jOOQ查詢將能夠呈現JPQL或其他某種查詢類型。 那將使3位訪客面對40-50種類型。 顯然,在這里,經典的訪客模式是一個錯誤的選擇。 但是我仍然想“訪問” QueryPart,將渲染和綁定委托給較低的抽象級別。

那么如何實現呢?

很簡單:堅持使用復合模式! 它允許您向每個人都必須實現的數據結構添加一些API元素。

因此,憑直覺,第一步就是

interface QueryPart {// Let the QueryPart return its SQLString getSQL();// Let the QueryPart bind variables to a prepared// statement, given the next bind index, returning// the last bind indexint bind(PreparedStatement statement, int nextIndex);
}

使用此API,我們可以輕松地抽象SQL查詢并將職責委派給較低級別??的工件。 例如,一個BetweenCondition。 它負責在[lower]和[upper]條件之間正確排序[field]的各部分,語法正確地呈現SQL,并將部分任務委派給其child-QueryParts:

class BetweenCondition {Field field;Field lower;Field upper;public String getSQL() {return field.getSQL() + ' between ' +lower.getSQL() + ' and ' +upper.getSQL();}public int bind(PreparedStatement statement, int nextIndex) {int result = nextIndex;result = field.bind(statement, result);result = lower.bind(statement, result);result = upper.bind(statement, result);return result;}
}

另一方面,BindValue主要負責變量綁定

class BindValue {Object value;public String getSQL() {return '?';}public int bind(PreparedStatement statement, int nextIndex) {statement.setObject(nextIndex, value);return nextIndex + 1;}
}

結合起來,我們現在可以輕松創建這種形式的條件: 在之間? 和?。 當實現更多QueryPart時,我們還可以想象像MY_TABLE.MY_FIELD BETWEEN嗎? 如果可以使用適當的字段實現,則使用AND(選擇?從雙精度)。 這就是使復合模式如此強大,通用的API和許多封裝行為的組件,從而將行為的一部分委派給子組件的原因。

第2步負責API的演變

到目前為止,我們已經看到了復合模式,非常直觀,但是功能非常強大。 但是遲早,我們將需要更多的參數,因為我們發現要將狀態從父級QueryPart傳遞給子級。 例如,我們希望能夠內聯某些子句的某些綁定值。 也許某些SQL方言不允許BETWEEN子句中的綁定值。 如何使用當前的API處理該問題? 擴展它,添加一個“布爾內聯”參數? 沒有! 這就是發明訪客模式的原因之一。 為了使復合結構元素的API保持簡單(只需執行“接受”)。 但是在這種情況下,用“上下文”替換參數比實現真正的訪客模式好得多:

interface QueryPart {// The QueryPart now renders its SQL to the contextvoid toSQL(RenderContext context);// The QueryPart now binds its variables to the contextvoid bind(BindContext context);
}

上面的上下文包含這樣的屬性(setter和render方法返回上下文本身,以允許方法鏈接):

interface RenderContext {// Whether we're inlining bind variablesboolean inline();RenderContext inline(boolean inline);// Whether fields should be rendered as a field declaration// (as opposed to a field reference). This is used for aliased fieldsboolean declareFields();RenderContext declareFields(boolean declare);// Whether tables should be rendered as a table declaration// (as opposed to a table reference). This is used for aliased tablesboolean declareTables();RenderContext declareTables(boolean declare);// Whether we should cast bind variablesboolean cast();// Render methodsRenderContext sql(String sql);RenderContext sql(char sql);RenderContext keyword(String keyword);RenderContext literal(String literal);// The context's 'visit' methodRenderContext sql(QueryPart sql);
}

BindContext也是如此。 如您所見,該API相當可擴展,可以添加新屬性,還可以添加其他常見的呈現SQL的方法。 但是BetweenCondition不必放棄有關如何呈現其SQL以及是否允許綁定變量的封裝知識。 它會將這些知識保留給自己:

class BetweenCondition {Field field;Field lower;Field upper;// The QueryPart now renders its SQL to the contextpublic void toSQL(RenderContext context) {context.sql(field).keyword(' between ').sql(lower).keyword(' and ').sql(upper);}// The QueryPart now binds its variables to the contextpublic void bind(BindContext context) {context.bind(field).bind(lower).bind(upper);}
}

另一方面,BindValue主要負責變量綁定

class BindValue {Object value;public void toSQL(RenderContext context) {context.sql('?');}public void bind(BindContext context) {context.statement().setObject(context.nextIndex(), value);}
}

結論:將其命名為上下文模式,而不是訪客模式

快速跳到訪客模式時要小心。 在許多情況下,您將使設計變得腫,從而使其完全不可讀且難以調試。 這里是要記住的規則,總結如下:

  1. 如果您有許多訪問者并且數據結構相對簡單(幾種類型),那么訪問者模式可能就可以了。
  2. 如果您有很多類型,并且訪問者組相對較少(很少有行為),則訪問者模式是過大的,請堅持使用復合模式
  3. 為了簡化API的演變,請將您的復合對象設計為具有采用單個上下文參數的方法。
  4. 突然之間,您將再次遇到“幾乎訪問者”模式,其中context = visitor,“ visit”和“ accept” =“您專有的方法名稱”

同時,“上下文模式”與“復合模式”一樣直觀,而與“訪問者模式”一樣強大,結合了兩個方面的優勢。

參考: 訪問者模式是我們的JCG合作伙伴 Lukas Eder在JAVA,SQL和JOOQ博客上再次訪問的 。


翻譯自: https://www.javacodegeeks.com/2012/05/visitor-pattern-re-visited.html

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/372698.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/372698.shtml
英文地址,請注明出處:http://en.pswp.cn/news/372698.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

java web開發技術大_2021年六大javaweb開發主流技術

作為歷史最為悠久的編程語言——java,歷經數十年依然盤踞在編程榜最前面的位置,這與它的技術和應用范圍是分不開的,同時呢,javaweb開發主流技術更是java開發者時時刻刻關注的問題,接下來我們一起分析一下2020年互聯網行…

ASP.NET—013:實現帶控件的彈出層(彈出框)

http://blog.csdn.net/yysyangyangyangshan/article/details/38458169 在頁面中用到彈出新頁面的情況比較多的,一般來說都是使用JS方法showModalDialog("新頁面相對路徑?參數1&參數2",window,"新頁面樣式");然后會新彈出一個模態的page頁。…

運維人員日常工作(轉自老男孩)

1)運維人員要謹記的6個字: 運維人員做事需遵循:簡單、易用、高效 (2)運維人員服務的3大宗旨: 1、企業數據安全保障。 2、7*24小時業務持續提供服務。 3、不斷提升用戶感受、體驗。 (3&#xff0…

c# 操作DatatTable

dtTemp.Columns.Add("列名");//增加一列 dtTemp.Columns.Remove("列名");//刪除一列 dtTemp.Columns["舊列名"].ColumnName "新列名";//修改列名 dtTemp.Columns["列名1"].SetOrdinal(dtTemp.Columns["列名2"].O…

java 二進制 歸屬權限_【Java EE 學習 75 上】【數據采集系統第七天】【二進制運算實現權限管理】【權限分析和設計】...

一、權限計算相關分析1.如何存儲權限首先說一下權限保存的問題,一個系統中最多有多少權限呢?一個大的系統中可能有成百上千個權限需要管理。怎么保存這么多的權限?首先,我們使用一個數字中的一位保存一種權限,那么如果…

MongoDB性能測試

因此,今天早上,我在mongo shell中四處亂逛。 我想出了三種不同的方式來聚合所需的數據,但不確定隨后應移植哪種代碼以在應用程序中使用。 那么,我將如何決定實施哪種方法呢? 好吧,讓我們選擇性能最佳的產品…

$_SERVER[SCRIPT_NAME]、$_SERVER[PHP_SELF]、$_SERVER[QUERY_STRING]、$_SERVER[REQUEST_URI]

1、$_SERVER["SCRIPT_NAME"] 說明:包含當前腳本的路徑 2、$_SERVER["PHP_SELF"] 說明:當前正在執行腳本的文件名 3、$_SERVER["QUERY_STRING"] 說明:查詢(query)的字符串 4、$_SERVER["REQUEST_URI"…

yii2增刪改查及AR的理解

yii2增刪改查 // 返回 id 為 1 的客戶 $customer Customer::findOne(1); // 返回 id 為 1 且狀態為 *active* 的客戶 $customer Customer::findOne([ id > 1, status > Customer::STATUS_ACTIVE, ]); // 返回id為1、2、3的一組客戶 $customers Customer::findAll([1, …

GWT和HTML5 Canvas演示

這是我對GWT和HTML5 Canvas的第一個實驗。 我的第一個嘗試是創建矩形,僅用幾行代碼就得出了這樣的內容: 碼: public class GwtHtml5 implements EntryPoint {static final String canvasHolderId "canvasholder";static final St…

mysql 平均值 排序_MySQL按平均兩個平均值排序

我正在競賽網站上工作,有兩種類型的用戶,普通網站成員和評委.每個人都可以使用拖放工具按照他們選擇的順序對特定比賽中的條目進行排序.完成后,相關的條目ID將附加一個排名值,然后可用于確定比賽中哪個條目獲得最高的平均分數.獲勝者實際上將通過平均每組的平均值來確定.我希望…

Solr管理界面詳解

轉載于:https://www.cnblogs.com/gslblog/p/6553813.html

iconv編碼轉換指令

看到一個不錯的指令iconv,可以對文件編碼進行轉換,記錄如下: iconv --list 列出所有支持轉換的編碼 icon -f code1 -t code2 filename -o newfile -f 即from 原來的編碼 -t 即to 新的編碼 filename 待轉換的文件名 -o newfile 要輸出的文件名 轉載于:htt…

使用Spring Roo進行快速云開發–第2部分:VMware Cloud Foundry

Spring Roo是在Java平臺上提供快速應用程序開發的工具。 我已經解釋了何時使用它: http : //www.kai-waehner.de/blog/2011/04/05/when-to-use-spring-roo 。 Spring Roo目前支持兩種針對云計算的解決方案:Google App Engine(GAE)…

java程序日期轉換_Java 日期轉換詳解及實例代碼

Java 日期轉換涉及的核心類:Date類、SimpleDateFormat類、Calendar類一、 Date型與long型Date型轉換為long型Date date new Date();//取得當前時間Date類型long date2long date.getTime();//Date轉longlong型轉換為Date型long cur System.currentTimeMills();//取…

軟件設計之思想

編程用何種語言不重要,重要的是其設計思想。轉載于:https://www.cnblogs.com/redfull/p/6554898.html

asp.net 與 java 2017_[ASP.net教程]C#與JAVA學習感悟

[ASP.net教程]C#與JAVA學習感悟0 2015-10-06 23:00:07C#與JAVA學習感悟學完C#與JAVA,感覺收獲良多。C#與JAVA這兩門語言相似度很高(了解它們早期歷史的人可能知道為什么),也許很多人在學習JAVA(或C#)時會同時學習C#(或JAVA),因為它們太相似了…

Spring和JSF集成:國際化和本地化

如果您正在開發針對多種語言的JSF應用程序&#xff0c;那么您可能很熟悉<f&#xff1a;loadBundle>標記。 即使您的應用程序不支持使用消息包的國際化仍然是一個好主意。 在<f&#xff1a;loadBundle>標記下&#xff0c;它從Java java.util.ResourceBundle中讀取消…

一個實用的卻被忽略的命名空間:Microsoft.VisualBasic:

當你看到這個命名空間的時候&#xff0c;別因為是vb的東西就匆忙關掉網頁&#xff0c;那將會是您的損失&#xff0c;此命名空間中的資源最初目的是為了簡化vb.net開發而創建的&#xff0c;所以microsoft.visualbasic并不屬于system命名空間&#xff0c;而是獨立存在的。雖然是為…

Linux基礎之命令練習Day2-useradd(mod,del),groupadd(mod,del),chmod,chown,

作業一&#xff1a; 1) 新建用戶natasha&#xff0c;uid為1000&#xff0c;gid為555&#xff0c;備注信息為“master” 2) 修改natasha用戶的家目錄為/Natasha 3) 查看用戶信息配置文件的最后一行 4) 為natasha用戶設置密碼“123” 5) 查看用戶密碼配置文件的最后一行 6) 將nat…

動態表單,JSF世界早已等待

新的PrimeFaces擴展版本0.5.0帶來了新的DynaForm組件。 通常&#xff0c;如果知道行/列的數量&#xff0c;元素的位置等&#xff0c;則可以通過h&#xff1a;panelGrid或p&#xff1a;panelGrid來構建非常簡單的表單。 對于靜態表單&#xff0c;這是正確的。 但是&#xff0c;如…