接口实现中多个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
的每个类中都有重复的foreach
和if
语句。
我想知道是否有更好的设计模式来处理这样的情况。比方说tommrow,我添加了一个从CodeTree
继承的新类——我必须遍历所有实现IComposer
的类并对它们进行修改。
我在考虑Visitor设计模式,但不确定是否以及如何实现它。Visitor是这个案例的正确解决方案吗?
移动与VariablesDecleration
和LoopsDecleration
中特定类相关的实现,在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对象可以保持更简单。