8

在我的代码上运行 FxCop,我收到以下警告:

Microsoft.Maintainability : 'FooBar.ctor 与来自 9 个不同命名空间的 99 种不同类型相结合。重写或重构方法以减少其类耦合,或考虑将方法移至与其紧密耦合的其他类型之一。40 以上的类耦合表示可维护性差,40 到 30 之间的类耦合表示可维护性中等,低于 30 的类耦合表示可维护性好。

我的班级是来自服务器的所有消息的登陆区。服务器可以向我们发送不同 EventArgs 类型的消息:

public FooBar()
{
    var messageHandlers = new Dictionary<Type, Action<EventArgs>>();
    messageHandlers.Add(typeof(YouHaveBeenLoggedOutEventArgs), HandleSignOut);
    messageHandlers.Add(typeof(TestConnectionEventArgs), HandleConnectionTest);
    // ... etc for 90 other types
}

“HandleSignOut”和“HandleConnectionTest”方法中的代码很少;他们通常将工作交给另一个类的函数。

如何以较低的耦合使这个类更好?

4

5 回答 5

15

让为他们感兴趣的事件注册工作的类……一个事件代理模式。

class EventBroker {
   private Dictionary<Type, Action<EventArgs>> messageHandlers;

   void Register<T>(Action<EventArgs> subscriber) where T:EventArgs {
      // may have to combine delegates if more than 1 listener
      messageHandlers[typeof(T)] = subscriber; 
   }

   void Send<T>(T e) where T:EventArgs {
      var d = messageHandlers[typeof(T)];
      if (d != null) {
         d(e);
      }
   }
}
于 2008-10-09T21:02:31.723 回答
5

您还可以使用某种 IoC 框架(如 Spring.NET)来注入字典。这样,如果你得到一个新的消息类型,你就不必重新编译这个中央集线器——只需更改一个配置文件。


期待已久的例子:

创建一个名为 Example 的新控制台应用程序,并添加以下内容:

using System;
using System.Collections.Generic;
using Spring.Context.Support;

namespace Example
{
    internal class Program
    {
        private static void Main(string[] args)
        {
            MessageBroker broker = (MessageBroker) ContextRegistry.GetContext()["messageBroker"];
            broker.Dispatch(null, new Type1EventArgs());
            broker.Dispatch(null, new Type2EventArgs());
            broker.Dispatch(null, new EventArgs());
        }
    }

    public class MessageBroker
    {
        private Dictionary<Type, object> handlers;

        public Dictionary<Type, object> Handlers
        {
            get { return handlers; }
            set { handlers = value; }
        }

        public void Dispatch<T>(object sender, T e) where T : EventArgs
        {
            object entry;
            if (Handlers.TryGetValue(e.GetType(), out entry))
            {
                MessageHandler<T> handler = entry as MessageHandler<T>;
                if (handler != null)
                {
                    handler.HandleMessage(sender, e);
                }
                else
                {
                    //I'd log an error here
                    Console.WriteLine("The handler defined for event type '" + e.GetType().Name + "' doesn't implement the correct interface!");
                }
            }
            else
            {
                //I'd log a warning here
                Console.WriteLine("No handler defined for event type: " + e.GetType().Name);
            }
        }
    }

    public interface MessageHandler<T> where T : EventArgs
    {
        void HandleMessage(object sender, T message);
    }

    public class Type1MessageHandler : MessageHandler<Type1EventArgs>
    {
        public void HandleMessage(object sender, Type1EventArgs args)
        {
            Console.WriteLine("Type 1, " + args.ToString());
        }
    }

    public class Type2MessageHandler : MessageHandler<Type2EventArgs>
    {
        public void HandleMessage(object sender, Type2EventArgs args)
        {
            Console.WriteLine("Type 2, " + args.ToString());
        }
    }

    public class Type1EventArgs : EventArgs {}

    public class Type2EventArgs : EventArgs {}
}

还有一个 app.config 文件:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <configSections>
    <sectionGroup name="spring">
      <section name="context" type="Spring.Context.Support.ContextHandler, Spring.Core"/>
      <section name="objects" type="Spring.Context.Support.DefaultSectionHandler, Spring.Core"/>
    </sectionGroup>
  </configSections>

  <spring>
    <context>
      <resource uri="config://spring/objects"/>
    </context>
    <objects xmlns="http://www.springframework.net">

      <object id="messageBroker" type="Example.MessageBroker, Example">
        <property name="handlers">
          <dictionary key-type="System.Type" value-type="object">
            <entry key="Example.Type1EventArgs, Example" value-ref="type1Handler"/>
            <entry key="Example.Type2EventArgs, Example" value-ref="type2Handler"/>
          </dictionary>
        </property>
      </object>
      <object id="type1Handler" type="Example.Type1MessageHandler, Example"/>
      <object id="type2Handler" type="Example.Type2MessageHandler, Example"/>
    </objects>
  </spring>
</configuration>

输出:

类型 1,Example.Type1EventArgs
类型 2,Example.Type2EventArgs
没有为事件类型定义处理程序:EventArgs

如您所见,MessageBroker不知道任何处理程序,处理程序也不知道MessageBroker. 所有的映射都在 app.config 文件中完成,因此如果您需要处理新的事件类型,可以将其添加到配置文件中。如果其他团队正在定义事件类型和处理程序,这尤其好 - 他们可以在 dll 中编译他们的东西,您将其放入您的部署中并简单地添加一个映射。

Dictionary 具有 object 类型的值,而不是MessageHandler<>因为实际的处理程序不能强制转换为MessageHandler<EventArgs>,所以我不得不稍微修改一下。我认为该解决方案仍然很干净,并且可以很好地处理映射错误。请注意,您还需要在此项目中引用 Spring.Core.dll。您可以在此处找到库,并在此处找到文档。依赖注入章节与此相关。另请注意,您没有理由为此需要使用 Spring.NET - 这里的重要思想是依赖注入。不知何故,需要告诉代理向 x 发送类型 a 的消息,并且使用 IoC 容器进行依赖注入是让代理不知道 x 的好方法,反之亦然。

与 IoC 和 DI 相关的其他一些 SO 问题:

于 2008-10-09T21:00:33.770 回答
0

也许不是为每条消息使用不同的类,而是使用标识消息的标志。

这将大大减少您拥有的消息数量并提高可维护性。我的猜测是大多数消息类的差异几乎为零。

很难选择另一种攻击方式,因为架构的其余部分(对我来说)是未知的。

例如,如果您查看 Windows,它本身并不知道如何处理可能被抛出的每条消息。相反,底层消息处理程序向主线程注册回调函数。

您可能会采取类似的方法。每个消息类都需要知道如何处理自己,并且可以将自己注册到更大的应用程序中。这应该会大大简化代码并摆脱紧耦合。

于 2008-10-09T20:51:30.483 回答
0

我看不到您的其余代码,但我会尝试创建数量少得多的 Event arg 类。相反,创建一些在包含的数据和/或您以后处理它们的方式方面彼此相似的字段,并添加一个字段来告诉您发生的确切类型的事件(可能您应该使用枚举)。

理想情况下,您不仅要使此构造函数更具可读性,而且还要使消息的处理方式(在单个事件处理程序中以类似方式处理的分组消息)

于 2008-10-09T21:01:21.367 回答
0

显然,您需要一种调度机制:根据您收到的事件,您希望执行不同的代码。

您似乎正在使用类型系统来识别事件,而它实际上是为了支持多态性。正如 Chris Lively 所建议的那样,您也可以(不滥用类型系统)使用枚举来识别消息。

或者,您可以拥抱类型系统的强大功能,并创建一个 Registry 对象,在其中注册每种类型的事件(通过静态实例、配置文件或其他方式)。然后你可以使用责任链模式来找到合适的处理程序。处理程序自己进行处理,或者它可能是一个工厂,创建一个处理事件的对象。

后一种方法看起来有点未指定和过度设计,但在 99 种事件类型(已经)的情况下,这对我来说似乎很合适。

于 2008-10-09T21:14:39.063 回答