簡體   English   中英

接口實現中多個if語句的最佳設計模式

[英]Best design pattern for multiple if statements in an interface implementation

我的c#項目中有一個IComposer接口:

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

CodeTree是一個基類,其中包含從CodeTree繼承的類的List<CodeTree> 例如:

public class VariablesDecleration : CodeTree
{
     //Impl
}

public class LoopsDecleration : CodeTree
{
     //Impl
}

我可以有一些實現IComposer類,並且每個類都有GenerateSnippet ,它們遍歷List<CodeTree>並基本上可以做到:

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

我有這個foreach以及在實現IComposer每個類中重復的if語句。

我想知道是否有更好的設計模式來處理這種情況。 假設明天我添加了一個從CodeTree繼承的新類-我將不得不研究實現IComposer所有類並對其進行修改。

我當時在考慮“訪客設計模式”-但是不確定,也不確定如何實施它。 訪客是否為此情況提供了正確的解決方案?

移動與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 invoke方法:

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

在上下文包含要處理的節點的地方,也許是將結果存儲在etc中的StringBuilder 。將其與ASP.NET中的HttpContext進行比較。

您可以為所有作曲家定義一個基類,並在其中實現GenerateSnippet,並避免為每個作曲家重寫此代碼。 另外,您可以通過實現composer.DoStuff();來改進foreach循環。 正如@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
}

首先,考慮將GenerateSnippet移出其他類繼承的基類。 SOLID的單一責任原則需要此。 作曲家必須作曲,而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對象。

將實現移動到特定類型的對象將不需要您在執行代碼中進行更改,而只需通過添加新類型(如果有的話)進行擴展即可。 如果您的實現細節有很大不同,則可以考慮使用Type Object Pattern 它將處理所有細節,您的CodeTree對象可以保持更簡單。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM