2012-10-17 54 views
1

這個問題不具體地約約適當類型的對象(或對象的適當構造函數)如何可以使用正則表達式進行標記化,但更匹配來處理標記器輸出的標記。OO策略的一組令牌匹配到適當的方法/構造

爲了解釋多一點,我的目標是解析包含令牌線成描述數據適當的對象的文本文件。我的解析器實際上已經完成,但目前是一個混亂的switch ... case聲明和我的問題的重點是我如何使用一個很好的面向對象方法重構這個。

首先,這裏有一個例子來說明我在做什麼。試想一下,包含很多項,如以下兩個文本文件:

cat 50 100 "abc" 
dog 40 "foo" "bar" 90 

在解析文件的這兩個特定行,我需要分別創建類CatDog實例。實際上,有相當多的不同對象類型被描述,並且有時會有不同數量的參數變體,如果這些值不是用來明確說明它們的話,通常會假定缺省值(這意味着通常使用構建器創建對象時的模式,或者某些類有幾個構造函數)。

每條線的初始標記都是使用一個Tokenizer類創建的,該類使用正則表達式組與每種類型的可能標記(整數,字符串和一些與此應用程序相關的其他特殊標記類型)相匹配的正則表達式組與PatternMatcher。從此標記生成類最終的結果是,對於每個它解析線,它提供回Token對象,其中每個Token具有原始值屬性沿.type屬性(指定整數,字符串等)的列表。

對於解析的每一行,我必須:

    對象類型
  • switchcase ...(第一令牌);
  • switch對參數的數量和選擇該數量的參數的適當構造 ;
  • 檢查每個標記類型是否適合構建該對象所需的參數類型;
  • 如果參數類型的數量或組合不適合要調用的對象類型,則記錄錯誤。

解析器我此刻有很多switch/caseif/else各地來處理這個地方,雖然它的工作原理,有相當大數量的對象類型,它變得有點笨拙。

有人建議可以替代,清潔和模式標記列表匹配到合適的方法調用更多的「OO」的方式?

回答

1

我已經做了類似的事情,我已經從代碼發佈器中解耦了我的解析器,除了解析本身,我認爲其他任何東西都沒有。我所做的是引入一個接口,解析器只要相信它已經找到語句或類似的程序元素,就會使用它來調用方法。在你的情況下,這些很可能是你在問題的例子中顯示的個別行。因此,無論何時解析一行,都會在界面上調用一個方法,其中的一個實現將負責其餘的部分。這樣你可以將程序代碼從解析中分離出來,並且兩者都可以自己完成(至少是解析器,因爲程序生成將實現解析器將使用的接口)。一些代碼來說明我的思路:

interface CodeGenerator 
{ 
    void onParseCat(int a, int b, String c); ///As per your line starting with "cat..." 
    void onParseDog(int a, String b, String c, int d); /// In same manner 
} 

class Parser 
{ 
    final CodeGenerator cg; 

    Parser(CodeGenerator cg) 
    { 
     this.cg = cg; 
    } 

    void parseCat() /// When you already know that the sequence of tokens matches a "cat" line 
    { 
     /// ... 

     cg.onParseCat(/* variable values you have obtained during parsing/tokenizing */); 
    } 
} 

這給你幾個優點,其中存在一個,你並不需要一個複雜的switch邏輯,你已經確定的語句/表達/元素的類型已經和調用正確的方法。您甚至可以在CodeGenerator接口中使用onParse之類的東西,如果您想始終使用相同的方法,則依靠Java方法重寫。還要記住,您可以使用Java在運行時查詢方法,這可以幫助您進一步刪除switch邏輯。

getClass().getMethod("onParse", Integer.class, Integer.class, String.class).invoke(this, catStmt, a, b, c); 

只是要注意上面的使用Integer類,而不是基本類型int,那你的方法必須覆蓋基於參數類型和數量 - 如果您在使用相同的參數序列兩種截然不同的聲明,上述5因爲至少會有兩個簽名相同的方法失敗。這當然是Java(和許多其他語言)中方法重寫的限制。

無論如何,你有幾種方法來實現你想要的。避免switch的關鍵是實現某種形式的虛擬方法調用,依靠內置的虛擬方法調用工具,或者使用靜態綁定爲特定的程序元素類型調用特定的方法。

當然,你至少需要一個switch聲明,您可以根據您的行開頭的字符串確定實際調用哪個方法。不是那個,就是引入了一個Map<String,Method>,它給了你一個運行時切換設施,在那裏地圖將把一個字符串映射到一個合適的方法,你可以調用invoke(Java的一部分)。我更喜歡在沒有大量案例的情況下保留switch,併爲更復雜的運行時方案保留Java Map

但是既然你談到「相當多的對象類型」,我可能會建議你引入一個運行時地圖並確實使用Map類。這取決於您的語言有多複雜,以及開始您的行的字符串是關鍵字還是更大集合中的字符串。

+1

是一種類型,而不是一個值。你需要Integer.class –

+0

是的,@Ricky,對不起 - 當然你是對的。 – amn

+0

非常感謝@amn;非常感謝你投入這個時間。它給了我一些啓發,儘管目前我還沒有完全理解這是否可以解決大量混亂的if/switch語句以確保接收到的令牌與每個對象構造函數的參數要求匹配的問題。例如:對於一個特定對象,我需要檢查是否有5個標記,其中4個是整數,最後是一個字符串;否則,記錄一個錯誤。如果我發佈一些示例代碼,這可能是最好的。與此同時,這裏有很好的靈感,特別是使用反射和地圖。 – Trevor

1

答案在這個問題上;你想有一個策略,基本上是一個地圖,其中的關鍵是,例如,「貓」和值的一個實例:

abstract class Argument<T> { 
    abstract T get(Map<Argument<?>, String> arguments); 
    private Argument() { 
    } 

    static Argument<Integer> intArgument(String name) { 
     return new Argument<Integer>() { 
      Integer get(Map<Argument<?>, String> arguments) { 
       return Integer.parseInt(arguments.get(this)); 
      } 
     }); 
    } 

    static Argument<String> stringArgument(String name) { 
     return new Argument<String>() { 
      String get(Map<Argument<?>, String> arguments) { 
       return arguments.get(this); 
      } 
     }); 
    } 
} 

final class CatCreator implements Creator { 
    final Argument<Integer> length = intArgument("length"); 
    final Argument<Integer> width = intArgument("width"); 
    final Argument<String> name = stringArgument("length"); 

    public List<Argument<?>> arguments() { 
     return asList(length, width, name); 
    } 

    public Cat create(Map<Argument<?>, String> arguments) { 
     return new Cat(length.get(arguments), width.get(arguments), name.get(arguments)); 
    } 
} 

支持的代碼,你將你的各種對象類型之間重用

我確定有人會發布需要更少代碼但使用反射的版本。選擇但是要記住編程錯誤的額外可能性,使其通過反思進行編譯。

+0

謝謝@Ricky Clarkson;你非常感謝你提供這個答案的時間。類似於我對amn的迴應,我不完全確定是否/如何這將有助於解決我不喜歡的關於我現在的代碼的主要事情:有很大的if/switch來執行驗證已經爲給定的對象類型解析了正確數量和類型的令牌。我可能只需要設計一些模式匹配方案來整理。我喜歡關於地圖的建議。我將在明天再次查看該代碼(當時更加清醒!),以確保我能夠正確地遵守它。 – Trevor

+0

那麼,給定一個Map 創建者,在將令牌cat讀入一個字符串之後,您可以調用creators.get(string)來獲得創建者,然後調用creator.arguments()來獲取列表>,讀取更多的標記,將它們添加到Map ,String> values中,並且當每個參數有一個字符串時,調用creator.get(values)以獲取結果對象。它可能需要一些調整來完全匹配你想要的,但我希望這是一個好的開始。 –