如何使用 C# 在命令模式中的命令之间共享相同的上下文?
How to share the same context between commands in Command-Pattern with C#?
我已经在我的应用程序中实现了命令模式(以多支持方式)。
结构:
class MultiCommand : BaseCommand
abstract class BaseCommand : ICommand
处理流程:
var commandsGroup = new MultiCommand(new List<ICommand>()
{
new Command1(),
new Command2(),
new Command3(),
});
commandsGroup.Execute()
现在,假设在 Command1
中更改了 somethingID
,我将在 Command2
中使用这个新值...而且,还有很多 在整个执行过程中受到影响的其他属性和对象。
此外,还有一些接口实现应该可以在任何命令中使用上下文对象,例如:
Context.ServerController.something();
IServerController
的实例化将在 multiCommandGroup
初始化之前进行。
如何为组中的所有命令共享一个 上下文?
上下文示例 class:
public class CommandContext
{
public IServerController ServerController;
public RequiredData Data { get; set; }
public CommandContext(){}
}
重要
最小的实现代码是 here
您可以在 BaseCommand
class(及其派生的 classes)上有一个构造函数,它可以接受某种 Context
class .在实例化属于同一组的命令时,您可以为它们提供所有相同的上下文对象。可能是这样的:
public class CommandContext
{
// The object that will be the target of the commands' actions.
public object Data { get; set; }
// ... any other properties that might be useful as shared state between commands...
}
public abstract class BaseCommand : ICommand
{
protected CommandContext Context { get; private set; }
public BaseCommand(CommandContext ctx)
{
Context = ctx;
}
}
public class ChangeSomethingIDCommand : BaseCommand
{
public ChangeSomethingIDCommand(CommandContext ctx) : base(ctx)
{ }
public void Execute()
{
var target = (SomeDomainClass)Context.Data;
target.SomethingID++;
}
}
// Elsewhere in your code (assuming 'myTargetDomainClassInstance' is
// a SomeDomainClass instance that has been instantiated elsewhere and
// represents the object upon which the commands will do work):
var ctx = new CommandContext { Data = myTargetDomainClassInstance };
var commandGroup = new MultiItemCommand(ctx, new List<ICommand>
{
new ChangeSomethingIDCommand(ctx),
new Command2(ctx),
new Command3(ctx)
});
commandGroup.Execute();
我建议做一些通用的东西。这是一个超级简单的例子。
class MultiCommand<TContext>
{
List<Command<TContext>> Commands;
TContext Context;
}
1) 如果你想保留这个接口,那么你必须将这个上下文作为构造函数参数传递:
new MultiCommand(new List<ICommand>()
{
new Command1(context),
new Command2(context),
new Command3(context),
})
2) 作为另一个选项,您可以接受委托列表而不是命令列表。
MultiCommand 将如下所示:
class MultiCommand : ICommand
{
public MultiCommand(List<Func<Context, Command>> commands, Context context)
}
这几乎是一样的,除了 MultiCommand 负责所有命令共享相同的上下文。
3) 看起来 MultiCommand 中的命令取决于上一个命令的结果。在这种情况下,命令模式可能不是最好的。也许你应该尝试在这里实现中间件链?
interface IMiddleware<TContext>
{
void Run(TContext context);
}
class Chain<TContext>
{
private List<IMiddleware<TContext>> handlers;
void Register(IMiddleware<TContext> m);
public void Run(TContext context)
{
handlers.ForEach(h => h.Run(context));
}
}
那么改变你的方法呢?我最近为 DDD 做了一个架构,执行命令意味着原子操作(从持久性中检索聚合根,应用域规则并坚持聚合)所以我不需要共享上下文并且可以批量处理多个命令而无需担心。
Here 你有一个 cqrs 架构,它使用命令模式和我发布的上述策略。
我的 0.02:
1) MultiCommand class 看起来像 Composite pattern。
您可能希望在基本命令 class 中添加 GetParentCommand() 方法,并在 MultiCommand class 中添加 AddChildCommand() 方法,它设置每个 children 的 parent。
然后 children 命令可以从其 parent 中获取上下文 object。 (Contextobject也应该定义在baseclass中。而且可以是泛型。)
编辑:
abstract class BaseCommand<T> : ICommand
{
public T Context { get; set; }
public BaseCommand Parent { get; set; }
}
class MultiCommand : BaseCommand
{
public void AddChildCommand(BaseCommand command)
{
command.parent = this; // we can get parent's context from children now
// put the command in an internal list
}
}
var commandsGroup = new MultiCommand();
commandsGroup.AddChildCommand(new Command1());
commandsGroup.AddChildCommand(new Command2());
commandsGroup.AddChildCommand(new Command3());
commandsGroup.Execute()
2) 我们可以创建一个全局单例上下文object。在 MultiCommand 的 Execute 函数中,我们可以在执行 children 的 Execute 函数之前设置当前上下文 object。然后 child 命令只能访问单例上下文 object。在执行完 children 之后,MultiCommand 可以重置上下文。 (这里的context其实就是一个stack。)
编辑:
abstract class BaseCommand : ICommand
{
// it could be put anywhere else as long as it can be accessed in command's Execute
// it can also be a stack
public static CommandContext Context {get; set;}
}
class MutliCommand : BaseCommand
{
public void Execute()
{
// do something to BaseCommand.Context
ChildCommand.Execute();
// do something to BaseCommand.Context
}
}
class ChildComand: BaseCommand
{
void Execute()
{
// do something with BaseCommand.Context
}
}
另一种选择是将上下文 object 作为 Execute 函数的参数:
class MultiCommand : BaseCommand
{
void Execute(CommandContext context)
{
Children.Execute(context);
}
}
考虑实用风格
public class SomeMainClass{
public void MultiCommandInit()
{
MultiCommand.New()
.Add(new Command1())
.Add(new Command2())
.Add(new Command3())
.SharedContext(CC => {
CC.Data = new RequiredData();
CC.ServerController = GetServerController();
});
}
private IServerController GetServerController()
{
// return proper instance of server controller
throw new NotImplementedException();
}
}
需要此扩展方法/功能...
public static class XMultiCommand
{
// How can I have a shared context like this for all Commands of the group?
public static MultiCommand SharedContext(this MultiCommand mc, Action<CommandContext> CallBack)
{
var cc = new CommandContext();
CallBack(cc);
mc.SharedContext = cc;
return mc;
}
}
最后,对 MultiCommand 的这些更改
public class MultiCommand
{
private System.Collections.Generic.List<ICommand> list;
public List<ICommand> Commands { get { return list; } }
public CommandContext SharedContext { get; set; }
public MultiCommand() { }
public MultiCommand(System.Collections.Generic.List<ICommand> list)
{
this.list = list;
}
public MultiCommand Add(ICommand cc)
{
list.Add(cc);
return this;
}
internal void Execute()
{
throw new NotImplementedException();
}
public static MultiCommand New()
{
return new MultiCommand();
}
}
使用函数式样式会发生很酷的事情
- 可重用性飙升!
- 高度关注单一职责问题
- 作文成为常态
- 代码维护变得简单
- Intellisense 成为您的内置 API(只需使用代码注释)
- 不需要激进的 OOP 设计模式
- 使用流畅的代码变得非常愉快
- 嵌套/装饰函数更容易想象和实现
- 你永远不会重复你自己
- Open/Closed 校长成为你的宗教
- 代码现在总是清晰、完整和简洁
- 有人甚至说不再需要接口
在您的情况下,如其他人所述,通过构造函数注入上下文就可以了。但总的来说,我会改为通过方法参数注入上下文:
public class Command1: BaseCommand
{
//inject as parameter instead
public void Execute(Context ctx)
{
}
}
原因是:
- 上下文应该由
CommandGroup
管理,这样我们就有更好的封装。
CommandGroup
负责执行其命令列表,因此 CommandGroup
可以向每个 Command
仅传递参数 Command
确实需要,这些参数可能在运行时构造(可能是之前的Commands
),这样就不可能传入这些对象作为我们构建命令列表的时间。因此,重用 Command
和简化单元测试变得更容易 Commands
因为我们不需要在单元测试中构造整个上下文对象。
也许你暂时不需要关心这些东西,但是方法注入提供了更多的灵活性。如果您使用过 .NET 中的某些框架,您会看到类似于 OwinContext
、FilterContext
、.. 它们作为参数传递并包含该上下文的相关信息。
在我看来,您的案例不适合命令模式。命令表示用户请求(操作),这些对象可以在运行时动态创建,但您是在编码时预定义命令。
您尝试做的事情看起来像 owin 中间件或 asp.net 网络 api 消息处理程序,它们是 http://www.dofactory.com/net/chain-of-responsibility-design-pattern
我已经在我的应用程序中实现了命令模式(以多支持方式)。
结构:
class MultiCommand : BaseCommand
abstract class BaseCommand : ICommand
处理流程:
var commandsGroup = new MultiCommand(new List<ICommand>()
{
new Command1(),
new Command2(),
new Command3(),
});
commandsGroup.Execute()
现在,假设在 Command1
中更改了 somethingID
,我将在 Command2
中使用这个新值...而且,还有很多 在整个执行过程中受到影响的其他属性和对象。
此外,还有一些接口实现应该可以在任何命令中使用上下文对象,例如:
Context.ServerController.something();
IServerController
的实例化将在 multiCommandGroup
初始化之前进行。
如何为组中的所有命令共享一个 上下文?
上下文示例 class:
public class CommandContext
{
public IServerController ServerController;
public RequiredData Data { get; set; }
public CommandContext(){}
}
重要 最小的实现代码是 here
您可以在 BaseCommand
class(及其派生的 classes)上有一个构造函数,它可以接受某种 Context
class .在实例化属于同一组的命令时,您可以为它们提供所有相同的上下文对象。可能是这样的:
public class CommandContext
{
// The object that will be the target of the commands' actions.
public object Data { get; set; }
// ... any other properties that might be useful as shared state between commands...
}
public abstract class BaseCommand : ICommand
{
protected CommandContext Context { get; private set; }
public BaseCommand(CommandContext ctx)
{
Context = ctx;
}
}
public class ChangeSomethingIDCommand : BaseCommand
{
public ChangeSomethingIDCommand(CommandContext ctx) : base(ctx)
{ }
public void Execute()
{
var target = (SomeDomainClass)Context.Data;
target.SomethingID++;
}
}
// Elsewhere in your code (assuming 'myTargetDomainClassInstance' is
// a SomeDomainClass instance that has been instantiated elsewhere and
// represents the object upon which the commands will do work):
var ctx = new CommandContext { Data = myTargetDomainClassInstance };
var commandGroup = new MultiItemCommand(ctx, new List<ICommand>
{
new ChangeSomethingIDCommand(ctx),
new Command2(ctx),
new Command3(ctx)
});
commandGroup.Execute();
我建议做一些通用的东西。这是一个超级简单的例子。
class MultiCommand<TContext>
{
List<Command<TContext>> Commands;
TContext Context;
}
1) 如果你想保留这个接口,那么你必须将这个上下文作为构造函数参数传递:
new MultiCommand(new List<ICommand>()
{
new Command1(context),
new Command2(context),
new Command3(context),
})
2) 作为另一个选项,您可以接受委托列表而不是命令列表。 MultiCommand 将如下所示:
class MultiCommand : ICommand
{
public MultiCommand(List<Func<Context, Command>> commands, Context context)
}
这几乎是一样的,除了 MultiCommand 负责所有命令共享相同的上下文。
3) 看起来 MultiCommand 中的命令取决于上一个命令的结果。在这种情况下,命令模式可能不是最好的。也许你应该尝试在这里实现中间件链?
interface IMiddleware<TContext>
{
void Run(TContext context);
}
class Chain<TContext>
{
private List<IMiddleware<TContext>> handlers;
void Register(IMiddleware<TContext> m);
public void Run(TContext context)
{
handlers.ForEach(h => h.Run(context));
}
}
那么改变你的方法呢?我最近为 DDD 做了一个架构,执行命令意味着原子操作(从持久性中检索聚合根,应用域规则并坚持聚合)所以我不需要共享上下文并且可以批量处理多个命令而无需担心。
Here 你有一个 cqrs 架构,它使用命令模式和我发布的上述策略。
我的 0.02:
1) MultiCommand class 看起来像 Composite pattern。
您可能希望在基本命令 class 中添加 GetParentCommand() 方法,并在 MultiCommand class 中添加 AddChildCommand() 方法,它设置每个 children 的 parent。
然后 children 命令可以从其 parent 中获取上下文 object。 (Contextobject也应该定义在baseclass中。而且可以是泛型。)
编辑:
abstract class BaseCommand<T> : ICommand
{
public T Context { get; set; }
public BaseCommand Parent { get; set; }
}
class MultiCommand : BaseCommand
{
public void AddChildCommand(BaseCommand command)
{
command.parent = this; // we can get parent's context from children now
// put the command in an internal list
}
}
var commandsGroup = new MultiCommand();
commandsGroup.AddChildCommand(new Command1());
commandsGroup.AddChildCommand(new Command2());
commandsGroup.AddChildCommand(new Command3());
commandsGroup.Execute()
2) 我们可以创建一个全局单例上下文object。在 MultiCommand 的 Execute 函数中,我们可以在执行 children 的 Execute 函数之前设置当前上下文 object。然后 child 命令只能访问单例上下文 object。在执行完 children 之后,MultiCommand 可以重置上下文。 (这里的context其实就是一个stack。)
编辑:
abstract class BaseCommand : ICommand
{
// it could be put anywhere else as long as it can be accessed in command's Execute
// it can also be a stack
public static CommandContext Context {get; set;}
}
class MutliCommand : BaseCommand
{
public void Execute()
{
// do something to BaseCommand.Context
ChildCommand.Execute();
// do something to BaseCommand.Context
}
}
class ChildComand: BaseCommand
{
void Execute()
{
// do something with BaseCommand.Context
}
}
另一种选择是将上下文 object 作为 Execute 函数的参数:
class MultiCommand : BaseCommand
{
void Execute(CommandContext context)
{
Children.Execute(context);
}
}
考虑实用风格
public class SomeMainClass{
public void MultiCommandInit()
{
MultiCommand.New()
.Add(new Command1())
.Add(new Command2())
.Add(new Command3())
.SharedContext(CC => {
CC.Data = new RequiredData();
CC.ServerController = GetServerController();
});
}
private IServerController GetServerController()
{
// return proper instance of server controller
throw new NotImplementedException();
}
}
需要此扩展方法/功能...
public static class XMultiCommand
{
// How can I have a shared context like this for all Commands of the group?
public static MultiCommand SharedContext(this MultiCommand mc, Action<CommandContext> CallBack)
{
var cc = new CommandContext();
CallBack(cc);
mc.SharedContext = cc;
return mc;
}
}
最后,对 MultiCommand 的这些更改
public class MultiCommand
{
private System.Collections.Generic.List<ICommand> list;
public List<ICommand> Commands { get { return list; } }
public CommandContext SharedContext { get; set; }
public MultiCommand() { }
public MultiCommand(System.Collections.Generic.List<ICommand> list)
{
this.list = list;
}
public MultiCommand Add(ICommand cc)
{
list.Add(cc);
return this;
}
internal void Execute()
{
throw new NotImplementedException();
}
public static MultiCommand New()
{
return new MultiCommand();
}
}
使用函数式样式会发生很酷的事情
- 可重用性飙升!
- 高度关注单一职责问题
- 作文成为常态
- 代码维护变得简单
- Intellisense 成为您的内置 API(只需使用代码注释)
- 不需要激进的 OOP 设计模式
- 使用流畅的代码变得非常愉快
- 嵌套/装饰函数更容易想象和实现
- 你永远不会重复你自己
- Open/Closed 校长成为你的宗教
- 代码现在总是清晰、完整和简洁
- 有人甚至说不再需要接口
在您的情况下,如其他人所述,通过构造函数注入上下文就可以了。但总的来说,我会改为通过方法参数注入上下文:
public class Command1: BaseCommand
{
//inject as parameter instead
public void Execute(Context ctx)
{
}
}
原因是:
- 上下文应该由
CommandGroup
管理,这样我们就有更好的封装。 CommandGroup
负责执行其命令列表,因此CommandGroup
可以向每个Command
仅传递参数Command
确实需要,这些参数可能在运行时构造(可能是之前的Commands
),这样就不可能传入这些对象作为我们构建命令列表的时间。因此,重用Command
和简化单元测试变得更容易Commands
因为我们不需要在单元测试中构造整个上下文对象。
也许你暂时不需要关心这些东西,但是方法注入提供了更多的灵活性。如果您使用过 .NET 中的某些框架,您会看到类似于 OwinContext
、FilterContext
、.. 它们作为参数传递并包含该上下文的相关信息。
在我看来,您的案例不适合命令模式。命令表示用户请求(操作),这些对象可以在运行时动态创建,但您是在编码时预定义命令。
您尝试做的事情看起来像 owin 中间件或 asp.net 网络 api 消息处理程序,它们是 http://www.dofactory.com/net/chain-of-responsibility-design-pattern