接口实现中多个if语句的最佳设计模式

本文关键字:语句 最佳 设计模式 if 实现 接口 | 更新日期: 2023-09-27 18:27:28

我的c#项目中有一个IComposer接口:

public interface IComposer
{
    string GenerateSnippet(CodeTree tree);
}

CodeTree是一个基类,它包含从CodeTree继承的类的List<CodeTree>。例如:

public class VariablesDecleration : CodeTree
{
     //Impl
}
public class LoopsDecleration : CodeTree
{
     //Impl
}

我可以有几个实现IComposer的类,在每个类中,我都有在List<CodeTree>上循环的GenerateSnippet,基本上做:

foreach (CodeTree code in tree.Codes)
{
    if (code.GetType() == typeof(VariablesDecleration))
    {
        VariablesDecleration codeVariablesDecleration = (VariablesDecleration) code;
        // do class related stuff that has to do with VariablesDecleration
    }
    else if (code.GetType() == typeof(LoopsDecleration))
    {
        LoopsDecleration codeLoopsDecleration = (LoopsDecleration) code;
        // do class related stuff that has to do with LoopsDecleration
    }
}

我在实现IComposer的每个类中都有重复的foreachif语句。

我想知道是否有更好的设计模式来处理这样的情况。比方说tommrow,我添加了一个从CodeTree继承的新类——我必须遍历所有实现IComposer的类并对它们进行修改。

我在考虑Visitor设计模式,但不确定是否以及如何实现它。Visitor是这个案例的正确解决方案吗?

接口实现中多个if语句的最佳设计模式

移动与VariablesDeclerationLoopsDecleration中特定类相关的实现,在CodeTree中提供抽象实现。然后在循环中,在CodeTree中简单地调用该方法,而不需要if…else检查。

public class VariablesDecleration : CodeTree
{
    //Impl
    public void SomeStuff()
    {
        //... specific to Variables
    }
}
public class LoopsDecleration : CodeTree
{
    //Impl
    public void SomeStuff()
    {
        //... specific to Loops
    }
}
public class CodeTree : ICodeTree
{
    void SomeStuff();
}
foreach (CodeTree code in tree.Codes)
{
    code.SomeStuff();
}

根据评论,你可能需要这样的东西:

public interface IComposer
{
    string DoStuff();
}
public class LoopComposer1 : IComposer
{
    string DoStuff(){ .. }
}
public class VariableComposer1 : IComposer
{
    string DoStuff(){ .. }
}
public class ComposerCollection
{
    private IEnumerable<IComposer> composers;
    string GenerateSnippet()
    {
        foreach(var composer in composers)
        {
            composer.DoStuff();
        }
        ...
        ...
    }
}

当然,现在关系已经反转,您的代码树或其创建者必须为其定义composer集合。

您的问题是如何在不同类型的代码树节点上执行操作,对吧?

首先声明一个名为INodeActor的新接口,该接口为您提供了代码如何在代码树节点上操作的约定。它的定义看起来像这样:

public interface INodeActor
{
    bool CanAct(CodeTree treeNode);
    void Invoke(CodeTree treeNode);
}

现在您可以使用以下代码:

foreach (CodeTree code in tree.Codes)
{
    if (code.GetType() == typeof(VariablesDecleration))
    {
        VariablesDecleration codeVariablesDecleration = (VariablesDecleration) code;
        // do class related stuff that has to do with VariablesDecleration
    }
    else if (code.GetType() == typeof(LoopsDecleration))
    {
        LoopsDecleration codeLoopsDecleration = (LoopsDecleration) code;
        // do class related stuff that has to do with LoopsDecleration
    }
}

并将其分解:

public class VariablesDeclerationActor : INodeActor
{
    public void CanAct(CodeTree node)
    {
        return node.GetType() == typeof(VariablesDecleration);
    }
    public void Invoke(CodeTree node)
    {
         var decl = (VariablesDecleration)node;
         // do class related stuff that has to do with VariablesDecleration
    }
}
public class LoopsDeclerationActor : INodeActor
{
    public void CanAct(CodeTree node)
    {
        return node.GetType() == typeof(LoopsDecleration);
    }
    public void Invoke(CodeTree node)
    {
         var decl = (LoopsDecleration)node;
         // do class related stuff that has to do with LoopsDecleration
    }
}

作曲家

把作曲家想象成一个工作协调人。它不需要知道实际工作是如何完成的。它的职责是遍历代码树并将工作委托给所有注册的参与者。

public class Composer
{
    List<INodeActor> _actors = new List<INodeActor>();
    public void AddActor(INodeActor actor)
    {
        _actors.Add(actor);
    }
    public void Process(CodeTree tree)
    {
         foreach (CodeTree node in tree.Codes)
         {
             var actors = _actors.Where(x => x.CanAct(node));
             if (!actors.Any())
                 throw new InvalidOperationException("Got no actor for " + node.GetType());
             foreach (var actor in actors)
                 actor.Invoke(node);
         }
    }
}

用法

您可以随心所欲地自定义执行,而无需更改遍历或任何现有代码。因此,代码现在遵循SOLID原则。

var composer = new Composer();
composer.Add(new VariablesDeclerationActor());
composer.Add(new PrintVariablesToLog());
composer.Add(new AnalyzeLoops());

如果你想构建一个结果,你可以引入一个传递给INodeActor调用方法的上下文:

public interface INodeActor
{
    bool CanAct(CodeTree treeNode);
    void Invoke(InvocationContext context);
}

如果上下文包含要处理的节点,则可能是一个用于存储结果的StringBuilder。将其与ASP.NET中的HttpContext进行比较。

您可以为所有作曲家定义一个基类,并在其中实现GenerateSippet,避免为每个作曲家重新编写此代码。此外,您还可以通过实现composer来改进foreach循环。DoStuff();正如@Narayana所建议的那样。

public class Composer:IComposer
{
    string GenerateSnippet()
    {
        foreach (CodeTree code in tree.Codes)
        {
             if (code.GetType() == typeof(VariablesDecleration))
             {
                 VariablesDecleration codeVariablesDecleration = (VariablesDecleration) code;
               // do class related stuff that has to do with VariablesDecleration
             }
             else if (code.GetType() == typeof(LoopsDecleration))
             {
                  LoopsDecleration codeLoopsDecleration = (LoopsDecleration) code;
                 // do class related stuff that has to do with LoopsDecleration
             }
        }
    }
}

public class ClassA: Composer
{
}
public class ClassB: Composer
{
}

也许依赖反转会有所帮助?

class Program
{
    static void Main(string[] args)
    {
        var composer1 = new ComposerA(new Dictionary<Type, Func<CodeTree, string>>()
        {
            { typeof(VariablesDeclaration), SomeVariableAction },
            { typeof(LoopsDeclaration), SomeLoopAction }
        });
        var composer2 = new ComposerB(new Dictionary<Type, Func<CodeTree, string>>()
        {
            { typeof(VariablesDeclaration), SomeOtherAction }
        });
        var snippet1 = composer1.GenerateSnippet(new CodeTree() {Codes = new List<CodeTree>() {new LoopsDeclaration(), new VariablesDeclaration()}});
        var snippet2 = composer2.GenerateSnippet(new CodeTree() { Codes = new List<CodeTree>() { new VariablesDeclaration() } });
        Debug.WriteLine(snippet1); // "Some loop action Some variable action  some composer A spice"
        Debug.WriteLine(snippet2); // "Some other action  some composer B spice"
    }
    static string SomeVariableAction(CodeTree tree)
    {
        return "Some variable action ";
    }
    static string SomeLoopAction(CodeTree tree)
    {
        return "Some loop action ";
    }
    static string SomeOtherAction(CodeTree tree)
    {
        return "Some other action ";
    }
}
public interface IComposer
{
    string GenerateSnippet(CodeTree tree);
}
public class CodeTree
{
    public List<CodeTree> Codes;
}
public class ComposerBase
{
    protected Dictionary<Type, Func<CodeTree, string>> _actions;
    public ComposerBase(Dictionary<Type, Func<CodeTree, string>> actions)
    {
        _actions = actions;
    }
    public virtual string GenerateSnippet(CodeTree tree)
    {
        var result = "";
        foreach (var codeTree in tree.Codes)
        {
            result = string.Concat(result, _actions[codeTree.GetType()](tree));
        }
        return result;
    }
}
public class ComposerA : ComposerBase
{
    public ComposerA(Dictionary<Type, Func<CodeTree, string>> actions) : base(actions)
    {
    }
    public override string GenerateSnippet(CodeTree tree)
    {
        var result = base.GenerateSnippet(tree);
        return string.Concat(result, " some composer A spice");
    }
}
public class ComposerB : ComposerBase
{
    public ComposerB(Dictionary<Type, Func<CodeTree, string>> actions) : base(actions)
    {
    }
    public override string GenerateSnippet(CodeTree tree)
    {
        var result = base.GenerateSnippet(tree);
        return string.Concat(result, " some composer B spice");
    }
}
public class VariablesDeclaration : CodeTree
{
    //Impl
}
public class LoopsDeclaration : CodeTree
{
    //Impl
}

首先,考虑将GenerateSippet从其他类继承的基类中移出。SOLID的单一责任原则就是要这样。Composer必须进行创作,CodeTree必须只做自己的工作。

下一步是if-else块。我认为您可以使用简单的Dictionary来存储不同类型的CodeTree项目:

public interface IComposer {
    string GenerateSnippet(List<CodeTree> trees);
    void RegisterCodeTreeType<T>(T codeType) where T:CodeTree;
}
public abstract class ComposerBase {
    private readonly Dictionary<Type, CodeTree> _dictionary;
    public ComposerBase() {
        _dictionary = new Dictionary<Type, CodeTree>();
    }
    public void RegisterCodeTreeType<T>(T codeType) where T:CodeTree {
       _dicionary.Add(typeof(T), codeType);
    }
    public string GenerateSnippet(List<CodeTree> trees) {
        StringBuilder fullCode = new StringBuilder();
        foreach(var tree in trees) {
            fullCode.Append(_dictionary[tree.GetType()].GenerateSnippet();
        }
    }
}

希望你有个主意。您应该在应用程序启动时使用Composer RegisterCodeTreeType方法注册所有类型。现在,它不取决于你有多少类型。请注意,这只是一个快速代码,请小心使用。

好吧,正如你所意识到的,必须在类型检查上编写那些If语句是不好的,而且它违背了抽象和子类的目的。

您希望您的呼叫代码保持不变(OCP)

现在,CodeTree对象负责弄清楚每个具体实现的逻辑。问题是责任属于每一个具体问题。for循环应该只是转换为接口类型IComposer,并调用方法string GenerateSnippet(CodeTree tree);来获得结果。每一个具体的细节都应该处理。与其在CodeTree对象中循环,不如在IComposer对象中循环。

将实现移动到特定类型对象不需要在执行代码中进行更改,而只需要在发生这种情况时通过添加新类型来进行扩展。如果您的实现细节有很大不同,您可以查看类型对象模式。它将处理所有细节,并且您的CodeTree对象可以保持更简单。