了解开闭原则

Posted

技术标签:

【中文标题】了解开闭原则【英文标题】:Understanding the Open Closed Principle 【发布时间】:2011-03-24 08:28:11 【问题描述】:

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

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

在我看来,这个简单的脚本处理器很适合通过应用“开放封闭原则”进行重构。以$ 开头的行可能永远不会有不同的处理方式。但是,如果需要添加以! 开头的新指令怎么办?还是需要新的处理标识符(例如新的 switch-case)?

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

有什么建议吗?

【问题讨论】:

这不是责任链模式的主要候选者吗? 【参考方案1】:

使用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。它提供了更大的灵活性,因为源可以是从简单字符串到复杂流的任何内容。

更新 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 原则,该解决方案是可以的。

【讨论】:

好东西!我唯一要改变的是创建一个结构作为字典的包装器(这将返回默认或创建处理程序)。我也将 SB 范围添加到 ProcessContext,只要它也应该传递给处理程序。 我不会在上下文中公开 SB,而是添加一个用于附加内容的方法。为未来的变化提供更大的灵活性。 这里不是访客的例子吗 访问者模式是解决开放/封闭原则的一种方式。但是在这个例子中,所有的处理程序(访问者)都是预先注册的,因此命令服务是已知的。在访问者模式中,您可以随时接受(访问者契约的)任何实现。 @jgauffin 感谢这个例子。我会采用您的方法并走得更远,并在IMyHandler 中添加另一种方法boolean appliesTo(String line)。现在用字典交换一个简单的列表。要找到正确的IMyHandler,可以遍历列表并返回适用于该行的处理程序。这样一来,您也可以在同一程序结构中处理更新 2 的 ! 情况,而无需创建更多接口,因为对单个字符没有限制。只关心处理程序的顺序;较长的比赛需要先出现。

以上是关于了解开闭原则的主要内容,如果未能解决你的问题,请参考以下文章

开闭原则——面向对象程序设计原则

设计模式七大原则之开闭原则学习

面向对象原则之一 开放封闭原则(开闭原则)

开闭原则

设计模-设计原则-开闭原则

了解开闭原则