65.9K
CodeProject 正在变化。 阅读更多。
Home

MVVM Mediator 模式

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.96/5 (18投票s)

2009年4月9日

CPOL

2分钟阅读

viewsIcon

88763

MVVM Mediator 模式。

大约一年前,我的好朋友 Marlon Grech 写了一篇关于 MVC + M 的精彩文章。你可以在这里阅读 Marlon 的优秀文章。

本质上,Marlon 所做的是创建了一个消息系统,以便不同的 MVC 类可以相互通信。Marlon 的原始代码非常出色,但我只是不喜欢他使用 string 来传递消息的方式,除此之外,我的代码与他的代码几乎没有变化。所以,Marlon 干得好,你真是个高手。

无论如何,Marlon 最初文章背后的想法是使用 MVC 模式,并结合一个名为 Mediator 的附加类,该类了解消息以及在发生需要发送消息的事情时通知谁。

如今,大多数人会在开发 WPF 时使用 MVVM 模式。我有一个小型的演示应用程序,它执行以下操作。

  1. 它有 2 个 textbox,其中一个是可以写入的 textbox,用于通过 Mediator 发送消息。
  2. 第二个 textbox 通过第一个 textbox 更改时通过 Mediator 发送的消息进行刷新。

让我们看一些代码吧。

首先,一个辅助类(从 Marlon 的博客上借鉴而来)。

   1:  using System;
   2:  using System.Collections.Generic;
   3:  using System.Linq;
   4:  using System.Text;
   5:  
   6:  namespace MediatorDemo
   7:  {
   8:      /// <summary>
   9:      /// The multi dictionary is a dictionary that contains 
  10:      /// more than one value per key
  11:      /// </summary>
  12:      /// <typeparam name="T">The type of the key</typeparam>
  13:      /// <typeparam name="K">The type of the list contents</typeparam>
  14:      public class MultiDictionary<T, K>
  15:          : Dictionary<T, List<K>>
  16:      {
  17:  
  18:          #region Private Methods
  19:          //checks if the key is already present
  20:          private void EnsureKey(T key)
  21:          {
  22:              if (!ContainsKey(key))
  23:              {
  24:                  this[key] = new List<K>(1);
  25:              }
  26:              else
  27:              {
  28:                  if (this[key] == null)
  29:                      this[key] = new List<K>(1);
  30:              }
  31:          }
  32:          #endregion
  33:  
  34:          #region Public Methods
  35:          /// <summary>
  36:          /// Adds a new value in the Values collection
  37:          /// </summary>
  38:          /// <param name="key">The key where to place the 
  39:          /// item in the value list</param>
  40:          /// <param name="newItem">The new item to add</param>
  41:          public void AddValue(T key, K newItem)
  42:          {
  43:              EnsureKey(key);
  44:              this[key].Add(newItem);
  45:          }
  46:  
  47:  
  48:          /// <summary>
  49:          /// Adds a list of values to append to the value collection
  50:          /// </summary>
  51:          /// <param name="key">The key where to place the item in the value list</param>
  52:          /// <param name="newItems">The new items to add</param>
  53:          public void AddValues(T key, IEnumerable<K> newItems)
  54:          {
  55:              EnsureKey(key);
  56:              this[key].AddRange(newItems);
  57:          }
  58:  
  59:          /// <summary>
  60:          /// Removes a specific element from the dict
  61:          /// If the value list is empty the key is removed from the dict
  62:          /// </summary>
  63:          /// <param name="key">The key from where to remove the value</param>
  64:          /// <param name="value">The value to remove</param>
  65:          /// <returns>Returns false if the key was not found</returns>
  66:          public bool RemoveValue(T key, K value)
  67:          {
  68:              if (!ContainsKey(key))
  69:                  return false;
  70:  
  71:              this[key].Remove(value);
  72:  
  73:              if (this[key].Count == 0)
  74:                  this.Remove(key);
  75:  
  76:              return true;
  77:          }
  78:  
  79:          /// <summary>
  80:          /// Removes all items that match the prediacte
  81:          /// If the value list is empty the key is removed from the dict
  82:          /// </summary>
  83:          /// <param name="key">The key from where to remove the value</param>
  84:          /// <param name="match">The predicate to match the items</param>
  85:          /// <returns>Returns false if the key was not found</returns>
  86:          public bool RemoveAllValue(T key, Predicate<K> match)
  87:          {
  88:              if (!ContainsKey(key))
  89:                  return false;
  90:  
  91:              this[key].RemoveAll(match);
  92:  
  93:              if (this[key].Count == 0)
  94:                  this.Remove(key);
  95:  
  96:              return true;
  97:          }
  98:          #endregion
  99:      }
 100:  }

这只是允许为特定消息注册多个对象。

接下来是 Mediator,它是一个单例,并且知道如何针对回调发送和注册消息,这正是我修改的地方。在 Marlon 的原始代码中,他使用了 String,而我现在使用 Action<Object> delegate 作为回调。我认为这是一个小小的改进。

   1:  using System;
   2:  using System.Collections.Generic;
   3:  using System.Linq;
   4:  using System.Text;
   5:  
   6:  namespace MediatorDemo
   7:  {
   8:      /// <summary>
   9:      /// Available cross ViewModel messages
  10:      /// </summary>
  11:      public enum ViewModelMessages { UserWroteSomething = 1 };
  12:  
  13:  
  14:      public sealed class Mediator
  15:      {
  16:          #region Data
  17:          static readonly Mediator instance = new Mediator();
  18:          private volatile object locker = new object();
  19:  
  20:          MultiDictionary<ViewModelMessages, Action<Object>> internalList
  21:              = new MultiDictionary<ViewModelMessages, Action<Object>>();
  22:          #endregion
  23:  
  24:          #region Ctor
  25:          //CTORs
  26:          static Mediator()
  27:          {
  28:  
  29:  
  30:          }
  31:  
  32:          private Mediator()
  33:          {
  34:  
  35:          }
  36:          #endregion
  37:  
  38:          #region Public Properties
  39:  
  40:          /// <summary>
  41:          /// The singleton instance
  42:          /// </summary>
  43:          public static Mediator Instance
  44:          {
  45:              get
  46:              {
  47:                  return instance;
  48:              }
  49:          }
  50:  
  51:          #endregion
  52:  
  53:          #region Public Methods
  54:          /// <summary>
  55:          /// Registers a Colleague to a specific message
  56:          /// </summary>
  57:          /// <param name="callback">The callback to use 
  58:          /// when the message it seen</param>
  59:          /// <param name="message">The message to 
  60:          /// register to</param>
  61:          public void Register(Action<Object> callback, 
  62:              ViewModelMessages message)
  63:          {
  64:              internalList.AddValue(message, callback);
  65:          }
  66:  
  67:  
  68:          /// <summary>
  69:          /// Notify all colleagues that are registered to the 
  70:          /// specific message
  71:          /// </summary>
  72:          /// <param name="message">The message for the notify by</param>
  73:          /// <param name="args">The arguments for the message</param>
  74:          public void NotifyColleagues(ViewModelMessages message, 
  75:              object args)
  76:          {
  77:              if (internalList.ContainsKey(message))
  78:              {
  79:                  //forward the message to all listeners
  80:                  foreach (Action<object> callback in 
  81:                      internalList[message])
  82:                          callback(args);
  83:              }
  84:          }
  85:          #endregion
  86:  
  87:      }
  88:  }

那么它是如何工作的呢?让我们看一个通过中介者发送消息的 ViewModel,请注意下面 NotifyColleagues(..) 方法的使用。

   1:  using System;
   2:  using System.Collections.Generic;
   3:  using System.ComponentModel;
   4:  using System.Linq;
   5:  using System.Text;
   6:  using System.Windows.Input;
   7:  using System.Windows.Data;
   8:  
   9:  
  10:  namespace MediatorDemo
  11:  {
  12:      public class WritersViewModel : ViewModelBase
  13:      {
  14:          private String writerText = null;
  15:  
  16:  
  17:          public WritersViewModel()
  18:          {
  19:  
  20:  
  21:          }
  22:  
  23:          public String WriterText
  24:          {
  25:              get { return writerText; }
  26:              set
  27:              {
  28:                  writerText = value;
  29:                  NotifyChanged("WriterText");
  30:                  //alert others about this change
  31:                  //via Mediator
  32:                  Mediator.Instance.NotifyColleagues(
  33:                      ViewModelMessages.UserWroteSomething, 
  34:                          writerText);
  35:              }
  36:          }
  37:  
  38:      }
  39:  }

如何处理来自 Mediator 的消息呢?这很简单,尽管我们需要将 Object 的结果转换为我们期望的 Type。这是必要的,因为 Mediator 使用 Action<Object> 委托作为回调,其中 Object 当然可以是任何 Type

   1:  using System;
   2:  using System.Collections.Generic;
   3:  using System.ComponentModel;
   4:  using System.Linq;
   5:  using System.Text;
   6:  using System.Windows.Input;
   7:  
   8:  
   9:  namespace MediatorDemo
  10:  {
  11:      public class ReadersViewModel : ViewModelBase
  12:      {
  13:          private String readText = String.Empty;
  14:  
  15:  
  16:          public ReadersViewModel()
  17:          {
  18:              //register to the mediator for the 
  19:              //UserWroteSomething message
  20:              Mediator.Instance.Register(
  21:  
  22:                  //Callback delegate, when message is seen
  23:                  (Object o) =>
  24:                  {
  25:                      ReadText = (String)o;
  26:                  }, ViewModelMessages.UserWroteSomething);
  27:          }
  28:  
  29:  
  30:          public String ReadText
  31:          {
  32:              get { return readText; }
  33:              set
  34:              {
  35:                  readText = value;
  36:                  NotifyChanged("ReadText");
  37:              }
  38:          }
  39:      }
  40:  }

这真的就是全部了。你现在有一个 Writer ViewModel,它将通过 Mediator/Action<Object> delegate 回调通知一个完全断开连接的 Reader ViewModel 发生了更改,这些回调已由希望基于消息进行操作的类注册到 Mediator

正如我所说,这都要归功于 Marlon,Marlon 干得好!

还有 这里有一个小型的演示应用程序,供你玩耍。

© . All rights reserved.