![](/img/trans.png)
[英]Best Design Pattern implementation to convert different objects derived from the same interface
[英]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
所有類並對其進行修改。
我當時在考慮“訪客設計模式”-但是不確定,也不確定如何實施它。 訪客是否為此情況提供了正確的解決方案?
移動與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
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.