耦合太高 - 如何更好地设计这个类?

Jud*_*ngo 8 design-patterns coupling

在我的代码上运行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
}
Run Code Online (Sandbox Code Playgroud)

"HandleSignOut"和"HandleConnectionTest"方法中的代码很少; 他们通常将工作交给另一个班级的职能部门.

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

Mar*_*ett 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);
      }
   }
}
Run Code Online (Sandbox Code Playgroud)


Chr*_*org 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 {}
}
Run Code Online (Sandbox Code Playgroud)

和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>
Run Code Online (Sandbox Code Playgroud)

输出:

Type 1, Example.Type1EventArgs
Type 2, Example.Type2EventArgs
No handler defined for event type: EventArgs

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

Dictionary具有object类型的值,而不是MessageHandler<>因为实际的处理程序无法转换为MessageHandler<EventArgs>,所以我不得不破解它.我认为解决方案仍然很干净,它可以很好地处理映射错误.请注意,您还需要在此项目中引用Spring.Core.dll.你可以找到图书馆在这里,和文档在这里.在依赖注入一章是有关这一点.另请注意,没有理由需要使用Spring.NET - 这里的重要思想是依赖注入.不知何故,某些事情需要告诉代理将类型a的消息发送到x,并且使用IoC容器进行依赖注入是让代理不了解x的好方法,反之亦然.

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