12

次のコードに出くわしたとき 、単純なスクリプト ファイル パーサーの古いコードをリファクタリングしていました。

StringReader reader = new StringReader(scriptTextToProcess);
StringBuilder scope = new StringBuilder();
string line = reader.ReadLine();
while (line != null)
{
    switch (line[0])
    {
        case '$':
            // Process the entire "line" as a variable, 
            // i.e. add it to a collection of KeyValuePair.
            AddToVariables(line);
            break;
        case '!':
            // Depending of what comes after the '!' character, 
            // process the entire "scope" and/or the command in "line".
            if (line == "!execute")
                ExecuteScope(scope);
            else if (line.StartsWith("!custom_command"))
                RunCustomCommand(line, scope);
            else if (line == "!single_line_directive")
                ProcessDirective(line);

            scope = new StringBuilder();
            break;

        default:
            // No processing directive, i.e. add the "line" 
            // to the current scope.
            scope.Append(line);
            break;
    }

    line = reader.ReadLine();
}

この単純なスクリプト プロセッサは、「オープン クローズドの原則」を適用してリファクタリングするのに適しているように思えます。a で始まる行は、$おそらく別の方法で処理されることはありません。しかし、a で始まる新しいディレクティブ!を追加する必要がある場合はどうでしょうか? または、新しい処理識別子 (新しいスイッチケースなど) が必要ですか?

問題は、OCP を壊さずにディレクティブとプロセッサを簡単かつ正確に追加する方法を理解できなかったことです。!-case を使用してscope and/or を使用すると、 -caselineと同様に少しトリッキーになりdefaultます。

助言がありますか?

4

1 に答える 1

24

a を使用しDictionary<Char, YourDelegate>て、文字の処理方法を指定します。DefaultHandler文字キーが辞書に存在しない場合に呼び出します。

Add(char key, YourDelegate handler)誰でも特定の文字を扱えるようにするメソッドを追加します。

アップデート

インターフェースで作業する方が良いです:

/// <summary>
/// Let anyone implement this interface.
/// </summary>
public interface IMyHandler
{
    void Process(IProcessContext context, string line);
}

/// <summary>
/// Context information
/// </summary>
public interface IProcessContext
{
}


// Actual parser
public class Parser
{
    private Dictionary<char, IMyHandler> _handlers = new Dictionary<char, IMyHandler>();
    private IMyHandler _defaultHandler;

    public void Add(char controlCharacter, IMyHandler handler)
    {
        _handlers.Add(controlCharacter, handler);
    }

    private void Parse(TextReader reader)
    {
        StringBuilder scope = new StringBuilder();
        IProcessContext context = null; // create your context here.

        string line = reader.ReadLine();
        while (line != null)
        {
            IMyHandler handler = null;
            if (!_handlers.TryGetValue(line[0], out handler))
                handler = _defaultHandler;

            handler.Process(context, line);


            line = reader.ReadLine();
        }
    }
}

TextReader代わりに a を渡すことに注意してください。ソースは単純な文字列から複雑なストリームまで何でもよいため、柔軟性が大幅に向上します。

更新 2

私も!同様の方法で取り扱いを分割します。つまり、IMyHandler を処理するクラスを作成します。

public interface ICommandHandler
{
    void Handle(ICommandContext context, string commandName, string[] arguments);
}

public class CommandService : IMyHandler
{
    public void Add(string commandName, ICommandHandler handler) 
    {
    }

    public void Handle(IProcessContext context, string line)
    {
       // first word on the line is the command, all other words are arguments.
       // split the string properly

       // then find the corrext command handler and invoke it.
       // take the result and add it to the `IProcessContext`
    }
}

これにより、実際のプロトコルの処理とコマンドの追加の両方の柔軟性が向上します。機能を追加するために何も変更する必要はありません。したがって、Open/Closed およびその他の SOLID 原則に関するソリューションは問題ありません。

于 2011-03-24T08:31:40.193 に答える