gpt4 book ai didi

c# - 逆变?协方差?这种通用架构有什么问题......?

转载 作者:太空狗 更新时间:2023-10-29 20:20:30 25 4
gpt4 key购买 nike

我在设置命令处理架构时遇到了一些问题。我希望能够创建许多从 ICommand 派生的不同命令;然后,创建许多从 ICommandHandler 派生的不同命令处理程序;

这是我开始定义的接口(interface)和类:

interface ICommand {}

class CreateItemCommand : ICommand {}

interface ICommandHandler<TCommand> where TCommand : ICommand {
void Handle(TCommand command);
}

class CreateItemCommandHandler : ICommandHandler<CreateItemCommand> {
public void Handle(CreateItemCommand command) {
// Handle the command here
}
}

我有一个帮助程序类可以创建适当类型的命令:

class CommandResolver {
ICommand GetCommand(Message message) {
return new CreateItemCommand(); // Handle other commands here
}
}

还有一个创建适当处理程序的辅助类;这是我遇到麻烦的地方:

class CommandHandlerResolver {
public ICommandHandler<TCommand> GetHandler<TCommand>(TCommand command) {

// I'm using Ninject and have an instance of an IKernel
// The following code throws an exception despite having a proper binding
// _kernel.GetService(typeof(ICommandHandler<TCommand>))

var bindingType = typeof(ICommandHandler<>).MakeGenericType(command.GetType());
var handler = _kernel.GetService(bindingType);
return handler as ICommandHandler<TCommand>;
// handler will be null after the cast
}
}

主要运行方法如下

CommandResolver _commandResolver;
HandlerResolver _handlerResolver;

void Run() {

// message is taken from a queue of messages

var command = _commandResolver.GetCommand(message);

var handler = _handlerResolver.GetHandler(command);
// handler will always be null

handler.Handle(command);
}

我可以想出几种不同的方法来重构代码,我确信这些方法可以避免这个问题,但我发现自己对这个问题有点困惑,想更多地了解发生了什么。

这个设计看起来应该可行。

最佳答案

问题

您的问题是您混合了静态类型和运行时类型:您正在编写依赖于已构造泛型类型的代码,但随后您使用基本接口(interface)类型调用它。

让我们跟随您的主要流程:

你的 CommandResolver总是返回静态类型 ICommand .当你说:

var command = _commandResolver.GetCommand(message);
var handler = _handlerResolver.GetHandler(command);

command 的类型绑定(bind)到 ICommand然后传递给 GetHander ,调用 GetHandler<ICommand> .即 TCommand在此调用中始终绑定(bind)到 ICommand .

这是这里的主要问题。自 TCommand总是 ICommand ,做:

_kernel.GetService(typeof(ICommandHandler<TCommand>))

...不工作(它寻找 ICommandHandler<ICommand> 而内核没有);即使它确实有效,您也必须将其返回为 ICommandHandler<ICommand>因为那是方法的返回类型。

调用 GetHandler在不知道(在编译时)命令的真实类型的情况下,您失去了有效使用泛型的能力并且 TCommand变得毫无意义。

因此,您尝试解决这个问题:您的解析器使用命令的运行时类型( command.GetType() ) 反射式构造类型 ICommandHandler<SomeCommandType>并尝试在内核中找到那个

假设您已经为该类型注册了一些东西,您将得到一个 ICommandHandler<SomeCommandType> ,然后您将尝试将其转换为 ICommandHandler<ICommand> (请记住 TCommand 绑定(bind)到 ICommand )。这当然行不通,除非TCommandICommandHandler<TCommand> 中声明协变 ,因为您正在向上类型层次结构;但即使它做到了,那也不是你想要的,因为你会用 ICommandHandler<ICommand> 做什么?无论如何?

简单地说:您不能使用 ICommandHandler<SomeCommand>ICommandHandler<ICommand>因为这意味着你可以传递任何类型的 ICommand它会很高兴地处理它——这是不正确的。如果您想使用泛型类型参数,则必须在整个流程中将它们绑定(bind)到实际命令类型。

解决方案

这个问题的一个解决方案是保留 TCommand在命令和命令处理程序的整个解析过程中绑定(bind)到真实的命令类型,例如通过类似 FindHandlerAndHandle<TCommand>(TCommand command) 的东西并使用命令的运行时类型通过反射调用它。但这又臭又笨拙,并且有一个很好的理由:你在滥用泛型

通用类型参数旨在帮助您在编译时知道您想要的类型,或者您可以将其与另一个类型参数统一的内容。在这些情况下,您不知道运行时类型,尝试使用泛型只会妨碍您。

解决这个问题的一种更简洁的方法是,当你知道命令的类型时(当你为它编写一个处理程序时)将上下文与你不知道它的上下文(当你试图为一个命令找到一个处理程序时)分开通用命令)。做到这一点的一个好方法是使用“非类型化接口(interface),类型化基类”模式:

public interface ICommandHandler // Look ma, no typeparams!
{
bool CanHandle(ICommand command);
void Handle(ICommand command);
}

public abstract class CommandHandlerBase<TCommand> : ICommandHandler
where TCommand : ICommand
{
public bool CanHandle(ICommand command) { return command is TCommand; }
public void Handle(ICommand command)
{
var typedCommand = command as TCommand;
if (typedCommand == null) throw new InvalidCommandTypeException(command);

Handle(typedCommand);
}

protected abstract void Handle(TCommand typedCommand);
}

这是连接通用和非通用世界的常用方法:调用它们时使用非通用接口(interface),但在实现时利用通用基类。您的主要流程现在如下所示:

public void Handle(ICommand command)
{
var allHandlers = Kernel.ResolveAll<ICommandHandler>(); // you can make this a dependency

var handler = allHandlers.FirstOrDefault(h => h.CanHandle(command));
if (handler == null) throw new MissingHandlerException(command);

handler.Handle(command);
}

从命令的实际运行时类型不必与处理程序的类型一一匹配的意义上说,这也更加稳健,所以如果你有一个 ICommandHandler<SomeBaseCommandType>它可以处理 SomeDerivedCommandType 类型的命令,因此您可以为命令类型层次结构中的中间基类构建处理程序,或使用其他继承技巧。

关于c# - 逆变?协方差?这种通用架构有什么问题......?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/23810372/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com