理解开闭原则

发布于 2024-10-26 05:35:19 字数 1491 浏览 3 评论 0原文

当我遇到以下代码时,我正在重构简单脚本文件解析器的一些旧代码:

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

在我看来,这个简单的脚本处理器是通过应用以下代码进行重构的良好候选者 : “开放封闭原则”。以 $ 开头的行可能永远不会以不同的方式处理。但是,如果需要添加以 ! 开头的新指令怎么办?或者需要新的处理标识符(例如新的开关盒)?

问题是,我不知道如何在不破坏 OCP 的情况下轻松、正确地添加更多指令和处理器。使用 scope 和/或 line! 情况使得它有点棘手,也是如此默认情况。

有什么建议吗?

I was refactoring some old code of a simple script file parser when I came across the following code:

StringReader reader = new StringReader(scriptTextToProcess);
StringBuilder scope = new StringBuilder();
string line = reader.ReadLine();
while (line != null)
{
    switch (line[0])
    {
        case '

This simple script processor seems to me like a good candidate for refactoring by applying the "open closed principle". The lines beginning with a $ will probably never be handled differently. But, what if new directives beginning with a ! needs to be added? Or new processing identifiers (e.g. new switch-cases) are needed?

The problem is, I could not figure out how to easily and correctly add more directives and processors without breaking OCP. The !-case using scope and/or line makes it a bit tricky, as does the default-case.

Any suggestions?

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

This simple script processor seems to me like a good candidate for refactoring by applying the "open closed principle". The lines beginning with a $ will probably never be handled differently. But, what if new directives beginning with a ! needs to be added? Or new processing identifiers (e.g. new switch-cases) are needed?

The problem is, I could not figure out how to easily and correctly add more directives and processors without breaking OCP. The !-case using scope and/or line makes it a bit tricky, as does the default-case.

Any suggestions?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

哥,最终变帅啦 2024-11-02 05:35:19

使用 Dictionary 指定应如何处理字符。如果字典中不存在字符键,则调用 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 。它提供了更大的灵活性,因为源可以是从简单字符串到复杂流的任何内容。

更新 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`
    }
}

这为处理实际协议和添加更多命令提供了更大的灵活性。您无需更改任何内容即可添加更多功能。因此,该解决方案在开放/封闭和其他一些 SOLID 原则方面是可以的。

Use a Dictionary<Char, YourDelegate> to specify how a character should be handled. Call DefaultHandler if the character key do not exist in the dictionary.

Add a Add(char key, YourDelegate handler) method allowing anyone to handle a specific character.

Update

It's better to work with interfaces:

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

Note that I pass in a TextReader instead. It gives much more flexibility since the source can be anything from a simple string to a complex stream.

Update 2

I would also break up the ! handling in a similar way. i.e. Create a class that handles 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`
    }
}

That gives more flexibility for both handling the actual protocol and add more commands. you do not have to change anything to add more functionality. The solution is therefore OK regarding Open/Closed and some other SOLID principles.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文