对于 WPF Window 中介服务,使用简单注入器按键解析实例的替代方法是什么?

What is an alternative to resolving instances by key with Simple Injector for a WPF Window mediator service?

查看“Resolve instances by key”部分 在 Simple Injector 网站上,我尝试了建议的 IRequestHandlerFactory 实现来改进下面的代码,但是注释如:

Note: The need for keyed registration can be an indication of ambiguity in the application design and a sign of a Liskov Substitution Principle violation. Take a good look if each keyed registration shouldn’t have its own unique interface, or perhaps each registration should implement its own version of a generic interface.

Note: Please remember the previous note about ambiguity in the application design. In the given example the design would probably be better of by using a generic IRequestHandler<TRequest> interface. This would allow the implementations to be batch registered using a single line of code, saves you from using keys, and results in a configuration the is verifiable by the container.

让我很好奇。

Q:注释 (IRequestHandler) 中提到的实际实现如何寻找我的情况?我花了一段时间试图弄清楚它可能是什么但不能 想出一个可行的方法。

我目前拥有的

为了分离视图模型,我目前有一个视图和对话框创建器,它通过 MVVMLight 的 Messenger 侦听消息,并基于这些消息将 创建必要的 Windows 或要求的对话。

我注入一个 IWindowResolver,它是一个工厂(尽管实际上更像是一个服务定位器),它只是获取请求的 Window 类型 在组合根上使用简单注入器工厂模式。

我喜欢 RegisterViewHandler,因为它很清楚,并且在一个地方将消息和关联的 window 以及消息处理程序缝合在一起 而不是散布在代码中(例如 HandleEmailPopupMessage 不需要知道要获取的 window 的确切类型和模式 HandleEmailPopupMessage 所使用的也可以为简单的 window 创建和消息发送而通用)。但是,我相信 IWindowResolver 可能更像是一个定位器,更多的是将一些注册推送到组合根中。

次要问题 - 是某种 IRequestHandler easier/more robust/useful 或者只是将 Dictionary 下推到工厂级别Simple Injector doco 中的示例,足够整理了吗?

// Current IWindowResolver
interface IWindowResolver
{
   Window CreateWindow<TWindow>(TWindow windowType) where TWindow : class;
}

// Current Simple Injector IWindowResolver implementation
[UsedImplicitly]
private sealed class SimpleInjectorWindowFactory : IWindowFactory
{
   private readonly Container _container;

   public SimpleInjectorWindowFactory(Container container)
   {
      _container = container;
   }

   public Window CreateWindow<TWindow>(TWindow windowType) where TWindow : class
   {
      return _container.GetInstance<TWindow>() as Window;
   }
}

public class ShowEmailPopupFormMessage
{
   public ShowEmailPopupFormMessage()
   {
      Params = new ParamsMessage();
   }

   public class ParamsMessage
   {
      public string CustomerName { get; set; }
      public string EmailTo { get; set; }
   }

   public ParamsMessage Params { get; set; }
}

// Current ViewConstructor
class ViewConstructor
{
   IWindowResolver _windowResolver;
   Dictionary<Type, Type> _viewMap = new Dictionary<Type, Type>(); // Maps a message type to a particular window/view type

   public ViewConstructor(IWindowResolver windowResolver)
   {
      _windowResolver = windowResolver;
      RegisterViewHandler<ShowEmailPopupFormMessage, EmailPopupWindow>(HandleEmailPopupMessage);
   }

   private void RegisterViewHandler<TMessage, TWindow>(Action<TMessage> messageAction)
      where TMessage : class
      where TWindow : Window
   {
      if (_viewMap.ContainsKey(typeof(TMessage)))
      {
         throw new ArgumentException("View already registered");
      }

      // Store the map of Message type to Window type
      _viewMap[typeof(TMessage)] = typeof(TWindow);

      // Register with the message handler
      Messenger.Default.Register(this, messageAction);
   }

   private void HandleEmailPopupMessage(ShowEmailPopupFormMessage msg)
   {
      var frm = GetMappedWindow(msg.GetType());

      // We know that the View and it's associated ViewModel are now created
      // so we can send some initialization parameters to the view and or ViewModel
      Messenger.Send(msg.Params);

      frm.ShowDialog();
   }

   private Window GetMappedWindow<TMessage>(TMessage messageType)
   {
      var windowType = _viewMap[typeof(TMessage)];

      var frm = _windowResolver.CreateWindow(windowType);

      if (frm == null)
      {
         throw new ApplicationException("Window is not of the specified Type!");
      }

      // Hookup common events such as cleanup events
      frm.Unloaded += FormOnUnloaded;

      return frm;
   }

   private static void FormOnUnloaded(object sender, RoutedEventArgs eArgs)
   {
      var frm = sender as Window;

      if (frm == null)
      {
         return;
      }

      // Cleanup the ViewModel 
      var dataContext = frm.DataContext as ICleanup;

      if (dataContext != null)
      {
         dataContext.Cleanup();
      }
   }
}

public class EmailPopupWindow : Window
{
   // Window knows how to set it's datacontext's ViewModel (in this case EmailPopupVm) using the ViewModelLocator declared in XML.
   // The window does not handle any messages.
}

// The View Model for the EmailPopupWindow. ViewModelBase is from MVVMLight
public class EmailPopupVm : ViewModelBase
{
   public EmailPopupVm()
   {
      Messenger.Register<ShowEmailPopupFormMessage.ParamsMessage>(HandleParamsMessage);
   }

   private void HandleParamsMessage(ShowEmailPopupFormMessage.ParamsMessage msg)
   {
      // Initialize the ViewModel with the parameters
      this.CustomerName = msg.CustomerName;
      this.EmailTo = msg.EmailTo;
   }
}

更新

为清楚起见,ViewModel(现在添加到上面的示例代码中)实际上处理 ShowEmailPopupFormMessage.ParamsMessage。 Window 忽略任何消息。

总是很难深入研究特定设计的细节并提炼出在该应用程序上下文中实际正确的东西,因为通常会遗漏许多细节。但无论如何我都会试一试,如果我在这里有点偏离,请提前致歉。

在我看来,您缺少 类 可以处理消息的抽象;我们称它为 IMessageHandler<TMessage>:

public interface IMessageHandler<TMessage>
{
    void Handle(TMessage message);
}

接下来,似乎 windows 也链接到某些消息。所以我会为 windows 定义一个通用接口,例如:

public interface IWindow<TMessage>
{
    void ShowDialog();
}

接下来,您可能需要一些允许您发送和分派消息的抽象(尽管 MVVMLight 可能已经提供给您,但我对此并不熟悉):

public interface IMessageDispatcher
{
    void Dispatch(object message);
}

基于 IMessageHandler<TMessage> 抽象,我们现在可以创建一个处理程序来处理 ShowEmailPopupFormMessage:

public class ShowEmailPopupFormMessageHandler : IMessageHandler<ShowEmailPopupFormMessage>
{
    private readonly IWindow<ShowEmailPopupFormMessage> frm;
    public ShowEmailPopupFormMessageHandler(IWindow<ShowEmailPopupFormMessage> frm) {
        this.frm = frm;
    }

    public void Handle(ShowEmailPopupFormMessage message) {
        Messenger.Send(msg.Params);
        frm.ShowDialog();
    }
}

既然你的 EmailPopupWindow 处理 ShowEmailPopupFormMessage 消息,我们应该让它实现 IWindow<ShowEmailPopupFormMessage>:

public class EmailPopupWindow : Window, IWindow<ShowEmailPopupFormMessage>
{
    // window stuff here
}

现在剩下的就是将组合根中的所有内容连接起来:

// Composition Root
container.Register(typeof(IWindow<>), applicationAssemblies);
container.Register(typeof(IMessageHandler<>), applicationAssemblies);
container.RegisterSingleton<IMessageDispatcher>(
    new SimpleInjectorMessageDispatcher(container));
container.RegisterInitializer<Window>(frm => {
    frm.Unloaded += FormOnUnloaded;
});

请注意,IWindow<T>IMessageHandler<T> 实现都是使用批量注册进行连接的。另请注意,Window.Unload 事件的注册是在组合根中完成的。

SimpleInjectorMessageDispatcher 也是合成根的一部分:

private sealed class SimpleInjectorMessageDispatcher : IMessageDispatcher
{
    private readonly Container container;
    public SimpleInjectorMessageDispatcher(Container container) {
        this.container = container;
    }

    public void Dispatch(object message) {
        Type handlerType = typeof(IMessageHandler<>).MakeGenericType(message.GetType());
        dynamic handler = this.container.GetInstance(handlerType);
        handler.Handle((dynamic)message);
    }
}

就像我说的,我可能偏离了一点(或偏离了一英里),但希望这能给您一些关于如何使用泛型类型来解决这个问题的想法。通用类型的优点是它为您提供一致的设计,允许在类型的定义中刻录元数据,简化应用横切关注点(使用装饰器),并允许轻松注册。但是同样的限制也适用于通用抽象。他们必须遵循 SOLID 原则。所以他们必须集中和狭窄(最好只有一个成员在他们身上)。