將狀態機模式實現為流處理器

在我的上一個博客中,我說我真的以為某些“四人行”(GOF)模式已經過時了,如果不是過時的話肯定不受歡迎。 特別是我說過StateMachine不是那么有用,因為您通常會想到另一種更簡單的方式來執行您正在執行的事情,而不是使用它。 為了進行修改,無論是為了宣講過時的內容還是我在上一個博客末尾附加的丑陋的“ C”代碼,我都認為我將展示StateMachine在將Twitter推文轉換為HTML中的用法。

這個場景只是一次,不是虛構的,也不是難以捉摸的,但這是我前幾天要做的事情。 在這種情況下,我有一個應用程序剛剛為經過身份驗證的Twitter用戶下載了大量時間軸推文。 解析了XML(或JSON)并掌握了我需要格式化以進行顯示的推文。 問題在于它們是純文本格式,我需要將它們轉換為HTML,并添加錨標記以生成類似于twitter在twitter主頁上格式化相同內容時所執行的操作的方式。

僅供參考,可以使用Twitter API通過以下URL檢索用戶Tweets:

<a href="https://api.twitter.com/1/statuses/user_timeline.xml?include_entities=true&include_rts=true&screen_name=BentleyMotors&count=2" target="new">https://api.twitter.com/1/statuses/user_timeline.xml?include_entities=true&include_rts=true&screen_name=BentleyMotors&count=2</a>

…在這種情況下,用戶名是“ BentleyMotors”。 如果您在URL中指定XML格式,則會在文本標簽中返回一條tweet,其外觀如下所示:

Deputy PM Nick Clegg visits #Bentley today to tour Manufacturing facilities. #RegionalGrowthFund http://t.co/kX81aZmY http://t.co/Eet31cCA

……這需要轉換成如下形式:

Deputy PM Nick Clegg visits <a href=\"https://twitter.com/#!/search/%23Bentley\">#Bentley</a> today to tour Manufacturing facilities. 
<a href=\"https://twitter.com/#!/search/%23RegionalGrowthFund\">#RegionalGrowthFund</a> 
<a href=\"http://t.co/kX81aZmY\">t.co/kX81aZmY</a> 
<a href=\"http://t.co/Eet31cCA\">t.co/Eet31cCA</a>

解決此問題1的一個好主意是使用狀態機 ,該狀態機一次讀取一個輸入流,以查找主題標簽,用戶名和URL,并將其轉換為HTML錨標簽。 例如,從#Bentley上方的完整推文中

變成

<a href=\"https://twitter.com/#!/search/%23Bentley\"> #Bentley </a>

http://t.co/Eet31cCA

變成

<a href=\"http://t.co/Eet31cCA\"> t.co/Eet31cCA </a>

這意味著代碼必須找到每個以“#”或“ @”開頭的單詞或以“ http://”開頭的URL。

該狀態機的URL圖如下所示:

此實現與下面的GOF圖確實不同,在該應用程序中,我將狀態與事件/動作分開了。 這具有改善去耦的好處,并且動作可以與多個狀態關聯。

聚集你的狀態

構建任何狀態機時,要做的第一件事就是將您的狀態收集在一起。 在最初的GOF模式中,狀態是抽象類。 但是,為了簡化起見,我更喜歡使用更多現代枚舉。 該狀態機的狀態為:

public enum TweetState {OFF("Off - not yet running"), //RUNNING("Running - happily processing any old byte bytes"), //READY("Ready - found a space, so there's maybe soemthing to do, but that depends upon the next byte"), //HASHTAG("#HashTag has been found - process it"), //NAMETAG("@Name has been found - process it"), //HTTPCHECK("Checking for a URL starting with http://"), //URL("http:// has been found so capture the rest of the URL");private final String description;TweetState(String description) {this.description = description;}@Overridepublic String toString() {return "TweetState: " + description;}}

讀取字節

接下來需要的是一個類,該類一次讀取一個輸入流一個字節,獲取與機器當前狀態相關聯的動作類,并使用該動作處理該字節。 這是通過StateMachine類完成的,如下所示:

public class StateMachine<T extends Enum<?>> {private final byte[] inputBuffer = new byte[32768];private T currentState;private final Map<T, AbstractAction<T>> stateActionMap = new HashMap<T, AbstractAction<T>>();public StateMachine(T startState) {this.currentState = startState;}/*** Main method that loops around and processes the input stream*/public void processStream(InputStream in) {// Outer loop - continually refill the buffer until there's nothing// left to readtry {processBuffers(in);terminate();} catch (Exception ioe) {throw new StateMachineException("Error processing input stream: "+ ioe.getMessage(), ioe);}}private void processBuffers(InputStream in) throws Exception {for (int len = in.read(inputBuffer); (len != -1); len = in.read(inputBuffer)) {// Inner loop - process the contents of the Bufferfor (int i = 0; i < len; i++) {processByte(inputBuffer[i]);}}}/*** Deal with each individual byte in the buffer*/private void processByte(byte b) throws Exception {// Get the set of actions associated with this stateAbstractAction<T> action = stateActionMap.get(currentState);// do the action, get the next statecurrentState = action.processByte(b, currentState);}/*** The buffer is empty. Make sue that we tidy up*/private void terminate() throws Exception {AbstractAction<T> action = stateActionMap.get(currentState);action.terminate(currentState);}/*** Add an action to the machine and associated state to the machine. A state* can have more than one action associated with it*/public void addAction(T state, AbstractAction<T> action) {stateActionMap.put(state, action);}/*** Remove an action from the state machine*/public void removeAction(AbstractAction<T> action) {stateActionMap.remove(action); // Remove the action - if it's there}}

這里的關鍵方法是processByte(...)

/*** Deal with each individual byte in the buffer*/private void processByte(byte b) throws Exception {// Get the set of actions associated with this stateAbstractAction<T> action = stateActionMap.get(currentState);// do the action, get the next statecurrentState = action.processByte(b, currentState);}

對于每個字節,此方法都將從stateActionMap獲取與當前狀態關聯的動作。 然后調用該動作并執行更新當前狀態的操作,以準備下一個字節。

整理好狀態和狀態機之后,下一步就是編寫操作。 在這一點上,我通過創建一個AbstractAction類來更緊密地遵循GOF模式,該類使用以下方法處理每個事件:

public abstract T processByte(byte b, T currentState) throws Exception;

給定當前狀態,此方法將處理一個信息字節,并使用該字節返回下一個狀態。 AbstractAction的完整實現是:

public abstract class AbstractAction<T extends Enum<?>> {/*** This is the next action to take - See the Chain of Responsibility Pattern*/protected final AbstractAction<T> nextAction;/** Output Stream we're using */protected final OutputStream os;/** The output buffer */protected final byte[] buff = new byte[1];public AbstractAction(OutputStream os) {this(null, os);}public AbstractAction(AbstractAction<T> nextAction, OutputStream os) {this.os = os;this.nextAction = nextAction;}/*** Call the next action in the chain of responsibility* * @param b* The byte to process* @param state* The current state of the machine.*/protected void callNext(byte b, T state) throws Exception {if (nextAction != null) {nextAction.processByte(b, state);}}/*** Process a byte using this action* * @param b* The byte to process* @param currentState* The current state of the state machine* * @return The next state*/public abstract T processByte(byte b, T currentState) throws Exception;/*** Override this to ensure an action tides up after itself and returns to a* default state. This may involve processing any data that's been captured* * This method is called when the input stream terminates*/public void terminate(T currentState) throws Exception {// blank}protected void writeByte(byte b) throws IOException {buff[0] = b; // Write the data to the output directoryos.write(buff);}protected void writeByte(char b) throws IOException {writeByte((byte) b);}}

構建狀態機

到目前為止,我編寫的所有代碼都是通用的,可以一次又一次地重復使用2 ,所有這些都意味著下一步是編寫一些特定于域的代碼。 從上面的UML圖表中,您可以看到特定于域的操作是: DefaultActionReadyActionCaptureTags 。 在繼續描述它們的作用之前,您可能已經猜到我需要將某些動作注入StateMachine并將它們與TweetState關聯。 下面的JUnit代碼顯示了此操作的完成方式…

StateMachine<TweetState> machine = new StateMachine<TweetState>(TweetState.OFF);// Add some actions to the statemachine// Add the default actionmachine.addAction(TweetState.OFF, new DefaultAction(bos));machine.addAction(TweetState.RUNNING, new DefaultAction(bos));machine.addAction(TweetState.READY, new ReadyAction(bos));machine.addAction(TweetState.HASHTAG, new CaptureTag(bos, new HashTagStrategy()));machine.addAction(TweetState.NAMETAG, new CaptureTag(bos, new UserNameStrategy()));machine.addAction(TweetState.HTTPCHECK, new CheckHttpAction(bos));machine.addAction(TweetState.URL, new CaptureTag(bos, new UrlStrategy()));

從上面的代碼中,您可以看到DefaultAction被鏈接為OFFRUNNING狀態, ReadyAction被鏈接為READY狀態, CaptureTag操作被鏈接到HASHTAG,NAMETAGURL狀態,而HttpCheckAction被鏈接到HTTPCHECK狀態。

您可能已經注意到, CaptureTag操作鏈接到一個以上的狀態。 這很好,因為CaptureTag采用了Strategy模式來即時更改其行為。 因此,我使用一些通用代碼執行一個操作,在注入一個策略對象之后,它可以完成三件事。

寫作動作

回到編寫動作,首先要編寫的動作通常是DefaultAction ,這是在沒有有趣的事情發生時調用的動作。 這個動作愉快地獲取輸入字符并將它們放入輸出流,同時尋找某些字符或字符/狀態組合。 DefaultAction的核心是processByte(...)方法中的switch語句。

public class DefaultAction extends AbstractAction<TweetState> {public DefaultAction(OutputStream os) {super(os);}/*** Process a byte using this action* * @param b* The byte to process* @param currentState* The current state of the state machine*/@Overridepublic TweetState processByte(byte b, TweetState currentState) throws Exception {TweetState retVal = TweetState.RUNNING;// Switch state if a ' ' charif (isSpace(b)) {retVal = TweetState.READY;writeByte(b);} else if (isHashAtStart(b, currentState)) {retVal = TweetState.HASHTAG;} else if (isNameAtStart(b, currentState)) {retVal = TweetState.NAMETAG;} else if (isUrlAtStart(b, currentState)) {retVal = TweetState.HTTPCHECK;} else {writeByte(b);}return retVal;}private boolean isSpace(byte b) {return b == ' ';}private boolean isHashAtStart(byte b, TweetState currentState) {return (currentState == TweetState.OFF) && (b == '#');}private boolean isNameAtStart(byte b, TweetState currentState) {return (currentState == TweetState.OFF) && (b == '@');}private boolean isUrlAtStart(byte b, TweetState currentState) {return (currentState == TweetState.OFF) && (b == 'h');}}

從上面的代碼中,您可以看到中央switch語句正在檢查每個字節。 如果該字節是一個空格,則下一個字節可能是一個特殊字符:“#”表示井號標簽的開頭,“ @”表示名稱標簽的開頭,“ h”表示URL的開頭; 因此,如果找到空間,則DefaultAction將返回READY狀態,因為可能還有更多工作要做。 如果找不到空格,則它將返回RUNNING狀態,該狀態告訴StateMachine在讀取下一個字節時調用DefaultAction

DefaultAction還會在一行的開頭檢查特殊字符,作為推文的第一個字符,例如“#”,“ @”或“ h”。

現在,控制已傳遞回StateMachine對象,該對象從輸入流中讀取下一個字節。 由于狀態現在為READY ,因此對processByte(...)的下一次調用將檢索ReadyAction

@Overridepublic TweetState processByte(byte b, TweetState currentState) throws Exception {TweetState retVal = TweetState.RUNNING;switch (b) {case '#':retVal = TweetState.HASHTAG;break;case '@':retVal = TweetState.NAMETAG;break;case 'h':retVal = TweetState.HTTPCHECK;break;default:super.writeByte(b);break;}return retVal;}

ReadyActionswitch語句中,您可以看到它的責任是通過分別檢查'#','@'和'h'來確認代碼已找到井號,名稱或URL。 如果找到一個,則返回以下狀態之一: HASHTAGNAMETAGHTTPCHECKStateMachine

假設ReadyAction找到了一個'#'字符并返回了HASHTAG狀態,則StateMachine在讀取下一個字節時,將從stateActionMap中 插入帶有注入的HashTagStrategy類的CaptureTag類。

public class CaptureTag extends AbstractAction<TweetState> {private final ByteArrayOutputStream tagStream;private final byte[] buf;private final OutputStrategy output;private boolean terminating;public CaptureTag(OutputStream os, OutputStrategy output) {super(os);tagStream = new ByteArrayOutputStream();buf = new byte[1];this.output = output;}/*** Process a byte using this action* @param b* The byte to process* @param currentState* The current state of the state machine*/@Overridepublic TweetState processByte(byte b, TweetState currentState)throws Exception {TweetState retVal = currentState;if (b == ' ') {retVal = TweetState.READY; // fix 1output.build(tagStream.toString(), os);if (!terminating) {super.writeByte(' ');}reset();} else {buf[0] = b;tagStream.write(buf);}return retVal;}/*** Reset the object ready for processing*/public void reset() {terminating = false;tagStream.reset();}@Overridepublic void terminate(TweetState state) throws Exception {terminating = true;processByte((byte) ' ', state);}}

CaptureTag代碼背后的想法是,它捕獲字符并將它們添加到ByteArrayOutputStream中,直到檢測到空格或輸入緩沖區為空。 當檢測到空間時, CaptureTag調用其OutputStrategy接口,在這種情況下,該接口由HashTagStrategy實現。

public class HashTagStrategy implements OutputStrategy {/*** @see state_machine.tweettohtml.OutputStrategy#build(java.lang.String,* java.io.OutputStream)*/@Overridepublic void build(String tag, OutputStream os) throws IOException {String url = "<a href=\"https://twitter.com/#!/search/%23" + tag + "\">#" + tag + "</a>";os.write(url.getBytes());}
}

HashTagStrategy構建一個標簽搜索URL,并將其寫入輸出流。 將URL寫入流后, CaptureTag返回READY狀態-檢測到空格并將控制權返回給StateMachine

StateMachine讀取下一個字節,因此該過程繼續。

處理主題標簽只是該代碼可以處理的幾種可能方案之一,在演示此方案時,我試圖演示如何使用狀態機一次處理一個字節的輸入流,以實現一些預定義的解決方案。 。 如果您對其他場景的處理方式感興趣,請查看github上的源代碼。

綜上所述

總而言之,這不是您想定期使用的技術。 它很復雜,很難實現并且容易出錯,而且通常有一種更簡單的方法來解析傳入的數據。 但是,有用的時候卻很少,盡管它很復雜,但卻是一個好的解決方案,所以我建議將其保存在隱喻工具箱中,以備不時之需。

1有幾種方法可以解決這個難題,其中某些方法可能比State Machine更簡單,更簡單

2此版本的StateMachine于2006年編寫,用于處理XML。 在這種情況下,代碼必須解壓縮一些Base 64 XML字段,并且由于該模式是可重用的,所以我只是從我的代碼示例工具箱中將其挖掘出來,用于Tweet to HTML的情況。

3完整項目可在github上找到 ……

參考 Captain Debug的Blog博客上的JCG合作伙伴 Roger Hughes提供了將狀態機模式實現為流處理器 的信息 。


翻譯自: https://www.javacodegeeks.com/2012/05/implementing-state-machine-pattern-as.html

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

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

相關文章

android 自定義actionbar,如何讓android的actionbar浮動且透明

如上圖所示&#xff0c;谷歌地圖的actionbar是透明的&#xff0c;且浮動在整個布局之上&#xff0c;沒有占用布局空間。其實要做到這樣的效果&#xff0c;我們首先想到的是兩個方面&#xff1a;1.將讓actionbar浮動起來。2.給actionbar一個背景&#xff0c;可以為顏色也可以為圖…

CentOS 7安裝redis及php擴展

安裝remi源 # wget http://rpms.famillecollet.com/enterprise/remi-release-7.rpm # rpm -Uvh remi-release-7.rpm # sed -i -e "s/enabled1/enabled0/g" /etc/yum.repos.d/remi.repo 確認使用remi源時安裝的Redis版本。 安裝Redis 使用remi源yum安裝Redis。 # yum …

對Openshift上的Play Framework 2應用進行故障排除

Openshift故障排除 使用“ 自己動手”應用程序類型&#xff0c;您實際上可以有很大的自由度來支持幾乎可以在Linux機器上構建和運行的任何框架或服務器。 但是您必須做功課&#xff0c;并做一些研究。 因此&#xff0c;在本文中&#xff0c;我將向您展示一些我在使用Openshift和…

關于更換頭像的整個過程理解

之前我遇到一個問題&#xff0c;就是怎樣修改頭像&#xff0c;都沒有更改&#xff0c;后來把某個參數置為null&#xff0c;就解決了問題&#xff0c;但是知其然還要知其所以然&#xff0c;現在還是著重去梳理整個流程 頭像&#xff0c;需要關注的是3個變量&#xff1a; 本地地址…

Ajax與CustomErrors的尷尬

在ASP.NET程序中&#xff0c;為了給用戶顯示友好的錯誤信息&#xff0c;通常在web.config中進行如下的設置&#xff1a; <customErrors mode"RemoteOnly" defaultRedirect"/error/error.htm"> </customErrors> 但如果是一個ajax請求在服務端發…

JSF開發人員應該知道的5種有用方法

這篇文章的目的是總結一些JSF開發人員可以在日常工作中使用的便捷方法。 實用程序類是將所有方法放在一起的好地方。 我會稱此類為FacesAccessor。 第一種方法可能是最常用的方法。 它以給定名稱返回托管bean。 必須按faces-config.xml或注釋注冊該bean。 注入是好的&#xff0…

android項目編碼規范,Android 項目規范

Android 項目規范本文檔的目的是定義項目規范。這些應遵循整個 Android 項目以幫助我們保持整潔和統一的代碼庫。 &#x1f642;

Java創建WebService服務及客戶端實現

簡介 WebService是一種服務的提供方式&#xff0c;通過WebService&#xff0c;不同應用間相互間調用變的很方便&#xff0c;網絡上有很多常用的WebService服務&#xff0c;如&#xff1a;http://developer.51cto.com/art/200908/147125.htm&#xff0c;不同的語言平臺對…

01-17權限管理

管理頁面&#xff1a; <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><html xmlns"http://www.w3.org/1999/xhtml"><head><meta http-equi…

Java靜態方法可能會產生代碼異味

代碼氣味的定義 &#xff08;來自維基百科&#xff09;&#xff1a; “程序源代碼中任何可能表明存在更深層問題的癥狀。” 在Java中&#xff0c; 靜態方法允許您在“類范圍”內執行代碼&#xff0c;而不是像成員方法這樣的實例范圍。 這意味著&#xff0c;它們依賴于類級別的變…

android json 解析圖片,JSON解析并獲取android中的圖像

我想解析包含字符串和圖像的JSON對象。我的代碼正在工作&#xff0c;但它加載圖像太慢。我想加載另一個asynctask或服務的圖像&#xff0c;以減少加載時間。我怎樣才能做到這一點&#xff1f;哪一個是最好的方法使用asynctask或服務&#xff1f;這里是我的代碼JSON解析并獲取an…

Node Express4.x 片段視圖 partials

1.在Express 4.x使用片段視圖&#xff0c;需要引入partials模塊 步驟&#xff1a; 1.在全局中安裝express-partials模塊&#xff1a; 2.在本地模塊中安裝express-partials,將模塊安裝到package.json中&#xff1a; 3.在入口文件(如&#xff1a;app.js)中引入模塊&#xff1a; v…

bzoj1690:[Usaco2007 Dec]奶牛的旅行(分數規劃+spfa判負環)

PS:此題數組名皆引用&#xff1a;戳我 題目大意&#xff1a;有n個點m條有向邊的圖&#xff0c;邊上有花費&#xff0c;點上有收益&#xff0c;點可以多次經過&#xff0c;但是收益不疊加&#xff0c;邊也可以多次經過&#xff0c;但是費用疊加。求一個環使得收益和/花費和最大&…

安全密碼存儲–請勿做的事和Java示例

安全存儲密碼的重要性 作為軟件開發人員&#xff0c;我們最重要的職責之一就是保護用戶的個人信息。 沒有我們應用程序的技術知識&#xff0c;用戶別無選擇&#xff0c;只能相信我們正在履行這一責任。 令人遺憾的是&#xff0c;在密碼方面&#xff0c;軟件開發社區的記錄不一。…

紅米note4x Android7,紅米Note4X能升級安卓7.0嗎?紅米Note4X如何升級Android7.0?

歡迎來到PPL網站的行業資訊知識分類&#xff0c;你現在觀看的這篇文章要和大家分享的是關于紅米Note4X能升級安卓7.0嗎&#xff1f;紅米Note4X如何升級Android7.0&#xff1f;的一些相關內容&#xff0c;希望大家能夠感興趣&#xff0c;并且希望我們能夠幫助到你&#xff01;在…

java基礎----數字簽名算法的介紹

數字簽名&#xff08;又稱公鑰數字簽名&#xff09;是一種類似寫在紙上的普通的物理簽名&#xff0c;但是使用了公鑰加密領域的技術實現&#xff0c;用于鑒別數字信息的方法。關于數字簽名的介紹&#xff0c;可以參見百度百科&#xff1a;http://baike.baidu.com/view/7626.htm…

Android宮格自動換行,九宮格視圖的布局及展示(相冊選擇)

上周一個朋友帶的項目出了點問題&#xff0c;招的ios開發人員在實現選取相冊圖片后用九宮格的樣式展示時遇到了瓶頸&#xff0c;花了將近2周都沒有解決。后來在跟我交流的過程中他把項目的圖片發給我看了下&#xff0c;看完我就笑了&#xff0c;這就只是個算法的問題&#xff0…

具有LCS方法的通用文本比較工具

常見的問題是檢測并顯示兩個文本&#xff08;尤其是幾百行或幾千行&#xff09;的差異。 使用純java.lang.String類方法可能是一種解決方案&#xff0c;但是對于此類操作最重要的問題是&#xff0c;“性能”將不能令人滿意。 我們需要一種有效的解決方案&#xff0c;其可能具有…

eclipse 開發 scala

(環境&#xff1a;jdk1.7,scala插件scala-2.1.1.2-site.zip) 1:下載scala插件 http://download.scala-ide.org/sdk/helium/e38/scala211/stable/site2&#xff1a;解壓到本地將這兩個文件里的jar包全部復制到eclipse的安裝目錄對應的文件夾里三&#xff1a;重啟eclipse這時會提…

關于這個博客

博客主要打算寫關于游戲制作方面的內容&#xff0c;包括directx&#xff0c;實時圖形知識等等方面的內容&#xff0c;作為一個渣暫時都是一些簡單的東西&#xff0c;努力找工作中...... 開這個博客主要目的是為了對自己做的事有個記錄吧&#xff0c;并且關于directx方面的東西本…