代码之家  ›  专栏  ›  技术社区  ›  Judah Gabriel Himango

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

  •  8
  • Judah Gabriel Himango  · 技术社区  · 16 年前

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

    Microsoft。可维护性: 'foobar.ctor与99 9种不同类型 命名空间。重写或重构 减少类耦合的方法, 或者考虑将方法移到一个 在其他类型中,它是紧密的 结合。上面的类耦合 40表示维修性差,a 40-30级耦合 表示可维护性中等, 以及30以下的一类耦合 表示可维护性良好。

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

    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”方法中的代码很少;它们通常将工作传递给另一个类中的函数。

    我怎样才能用低耦合使这门课更好呢?

    5 回复  |  直到 16 年前
        1
  •  15
  •   Mark Brackett Achilles Ram Nakirekanti    16 年前

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

    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);
          }
       }
    }
    
        2
  •  5
  •   Community arnoo    7 年前

    您还可以使用某种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>
    

    输出:

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

    正如你所看到的, MessageBroker 不知道任何处理程序,处理程序也不知道 消息代理 . 所有映射都在app.config文件中完成,因此如果需要处理新的事件类型,可以将其添加到配置文件中。如果其他团队正在定义事件类型和处理程序,这就特别好了——他们只需在一个DLL中编译他们的内容,然后将其放到部署中,然后简单地添加一个映射。

    字典的值为“对象”类型,而不是 MessageHandler<> 因为实际的处理程序不能强制转换为 MessageHandler<EventArgs> 所以我不得不在这方面做些修改。我认为解决方案仍然是干净的,并且它可以很好地处理映射错误。注意,您还需要在这个项目中引用spring.core.dll。你可以找到图书馆 here 以及文件 here . 这个 dependency injection chapter 与此相关。另外请注意,您没有必要为此使用Spring.net——这里的重要思想是依赖注入。不知何故,需要告诉代理将类型A的消息发送到X,并且使用IOC容器进行依赖项注入是让代理不了解X的好方法,反之亦然。

    关于国际奥委会和DI的一些其他问题:

        3
  •  0
  •   Grzenio    16 年前

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

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

        4
  •  0
  •   ChrisLively    16 年前

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

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

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

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

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

        5
  •  0
  •   xtofl Adam Rosenfield    16 年前

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

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

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

    后一种方法看起来有点未指定和过度设计,但是在99种事件类型(已经)的情况下,我觉得它是合适的。

    推荐文章