1

この質問は、正規表現を使用したトークン化の実行に関するものではなく、適切なタイプのオブジェクト (またはオブジェクトの適切なコンストラクター) を一致させて、トークナイザーからのトークン出力を処理する方法に関するものです。

もう少し説明すると、私の目的は、トークンの行を含むテキスト ファイルを解析して、データを説明する適切なオブジェクトにすることです。実際、私のパーサーはすでに完成していますが、現時点ではswitch...caseステートメントの混乱があり、私の質問の焦点は、優れた OO アプローチを使用してこれをリファクタリングする方法です。

まず、私が全体的に行っていることを説明するための例を示します。次の 2 つのような多くのエントリを含むテキスト ファイルを想像してください。

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

ファイルのこれら 2 つの特定の行を解析するとき、クラスCatDogそれぞれのインスタンスを作成する必要があります。実際には、非常に多くの異なるオブジェクト タイプが記述されており、場合によっては引数の数の異なるバリエーションがあり、値が明示的に記述されていない場合はデフォルトが想定されることがよくあります (つまり、ビルダーを使用するのが通常は適切であることを意味します)。オブジェクトを作成するときのパターン、または一部のクラスには複数のコンストラクターがあります)。

各行の最初のトークン化は、可能なトークンの各タイプ (整数、文字列、およびこのアプリケーションに関連する他のいくつかの特別なトークン タイプ) とおよびTokenizerに一致する正規表現のグループを使用する、私が作成したクラスを使用して行われています。このトークナイザー クラスの最終結果は、解析する行ごとに、オブジェクトのリストを返します。各オブジェクトには、プリミティブ値のプロパティと共にプロパティ (整数、文字列などを指定) があります。PatternMatcherTokenToken.type

解析された行ごとに、次のことを行う必要があります。

  • switch...caseオブジェクトタイプ(最初のトークン);
  • switch引数の数に基づいて、その引数の数に適したコンストラクターを選択します。
  • 各トークン タイプが、オブジェクトの構築に必要な引数のタイプに適していることを確認します。
  • 引数の型の数または組み合わせが、呼び出されているオブジェクトの型に適していない場合は、エラーをログに記録します。

現時点で私が持っているパーサーには、これを処理するためのswitch/caseまたはif/elseがあちこちにあり、機能しますが、かなり多くのオブジェクト タイプがあるため、少し扱いに​​くくなっています。

誰かが、トークンのリストを適切なメソッド呼び出しにパターンマッチングする、よりクリーンでより「OO」な代替方法を提案できますか?

4

2 に答える 2

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 */);
    }
}

これにより、いくつかの利点が得られます。そのうちの 1 つはswitch、ステートメント/式/要素の型を既に決定しており、正しいメソッドを呼び出すため、複雑なロジックが必要ないことです。常に同じメソッドを使用する場合は、Java メソッドのオーバーライドに依存してonParse、インターフェイスのようなものを使用することもできます。CodeGeneratorまた、Java を使用して実行時にメソッドを照会できることも覚えておいてください。これは、switchロジックをさらに削除するのに役立ちます。

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

Integer上記はプリミティブ型の代わりにクラスを使用していること、およびパラメーターの型とカウントに基づいてメソッドをオーバーライドする必要があることに注意してintください。同じパラメーター シーケンスを使用する 2 つの異なるステートメントがある場合、少なくとも 2 つのメソッドがあるため、上記は失敗する可能性があります。同じサインで。もちろん、これは Java (および他の多くの言語) でのメソッドのオーバーライドの制限です。

いずれにせよ、目的を達成するための方法はいくつかあります。避けるべき重要なswitch点は、なんらかの形式の仮想メソッド呼び出しを実装するか、組み込みの仮想メソッド呼び出し機能に依存するか、静的バインディングを使用して特定のプログラム要素の型に対して特定のメソッドを呼び出すことです。

もちろん、行が始まる文字列に基づいて実際に呼び出すメソッドを決定するステートメントが少なくとも1 つ必要です。switchそれか、ランタイム スイッチ機能を提供する を導入するかのどちらかです。マップは、 (Java の一部) をMap<String,Method>呼び出すことができる適切なメソッドに文字列をマップします。私は、ケースの数がそれほど多くない場所を維持し、Javaをより複雑なランタイム シナリオ用に予約invokeすることを好みます。switchMap

しかし、「かなり大量のオブジェクトタイプ」について話しているので、ランタイムマップを導入してMap実際にクラスを使用することをお勧めします。それは、言語がどれほど複雑であるか、および行を開始する文字列がキーワードであるか、はるかに大きなセットの文字列であるかによって異なります。

于 2012-10-17T12:09:04.510 に答える
1

答えは質問にありました。基本的に、キーが「cat」などのマップであり、値が次のインスタンスである戦略が必要です。

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));
    }
}

さまざまなオブジェクト タイプ間で再利用するサポート コード:

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);
            }
        });
    }
}

必要なコードが少ないがリフレクションを使用するバージョンを誰かが投稿すると確信しています。いずれかを選択しますが、リフレクションを使用してコンパイルを過ぎてしまうプログラミングの間違いの可能性を念頭に置いてください。

于 2012-10-17T12:25:49.327 に答える