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

发布于 2024-07-07 05:20:32 字数 781 浏览 10 评论 0原文

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

微软.可维护性: 'FooBar.ctor 与 99 耦合 9种不同类型 命名空间。 重写或重构 减少其类耦合的方法, 或者考虑将该方法移至一种 其他类型的它是紧密的 加上。 上面的A类耦合 40表示可维护性较差,a 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”方法中的代码很少; 他们通常将工作传递给另一个类中的函数。

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

Running FxCop on my code, I get this warning:

Microsoft.Maintainability :
'FooBar.ctor is coupled with 99
different types from 9 different
namespaces. Rewrite or refactor the
method to decrease its class coupling,
or consider moving the method to one
of the other types it is tightly
coupled with. A class coupling above
40 indicates poor maintainability, a
class coupling between 40 and 30
indicates moderate maintainability,
and a class coupling below 30
indicates good maintainability.

My class is a landing zone for all messages from the server. The server can send us messages of different EventArgs types:

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

The "HandleSignOut" and "HandleConnectionTest" methods have little code in them; they usually pass the work off to a function in another class.

How can I make this class better with lower coupling?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

情绪操控生活 2024-07-14 05:20:32

让执行工作的类注册他们感兴趣的事件... 事件代理模式。

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);
      }
   }
}

Have the classes that do the work register for events they're interested in...an event broker pattern.

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);
      }
   }
}
椵侞 2024-07-14 05:20:32

您还可以使用某种 IoC 框架(例如 Spring.NET)来注入字典。 这样,如果您获得新的消息类型,则无需重新编译此中央集线器 - 只需更改配置文件即可。


The long awaited example:

创建一个新的控制台应用程序,命名为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 不知道任何处理程序,而处理程序也不知道不了解 MessageBroker。 所有映射都在 app.config 文件中完成,因此如果您需要处理新的事件类型,可以将其添加到配置文件中。 如果其他团队正在定义事件类型和处理程序,这尤其好 - 他们只需将其内容编译到 dll 中,您将其放入部署中并只需添加映射即可。

字典具有对象类型的值,而不是 MessageHandler<> 因为实际的处理程序无法转换为 MessageHandler,所以我不得不解决这个问题少量。 我认为该解决方案仍然很干净,并且可以很好地处理映射错误。 请注意,您还需要在此项目中引用 Spring.Core.dll。 您可以在此处,以及文档此处依赖注入章节与此相关。 另请注意,您没有必要为此使用 Spring.NET - 这里的重要思想是依赖注入。 不知何故,需要告诉代理将类型 a 的消息发送到 x,并且使用 IoC 容器进行依赖项注入是让代理不知道 x 的好方法,反之亦然。

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

You could also use some sort of IoC framework, like Spring.NET, to inject the dictionary. This way, if you get a new message type, you don't have to recompile this central hub - just change a config file.


The long awaited example:

Create a new console app, named Example, and add this:

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 {}
}

And an app.config file:

<?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>

Output:

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

As you can see, MessageBroker doesn't know about any of the handlers, and the handlers don't know about MessageBroker. All of the mapping is done in the app.config file, so that if you need to handle a new event type, you can add it in the config file. This is especially nice if other teams are defining event types and handlers - they can just compile their stuff in a dll, you drop it into your deployment and simply add a mapping.

The Dictionary has values of type object instead of MessageHandler<> because the actual handlers can't be cast to MessageHandler<EventArgs>, so I had to hack around that a bit. I think the solution is still clean, and it handles mapping errors well. Note that you'll also need to reference Spring.Core.dll in this project. You can find the libraries here, and the documentation here. The dependency injection chapter is relevant to this. Also note, there is no reason you need to use Spring.NET for this - the important idea here is dependency injection. Somehow, something is going to need to tell the broker to send messages of type a to x, and using an IoC container for dependency injection is a good way to have the broker not know about x, and vice versa.

Some other SO question related to IoC and DI:

爱格式化 2024-07-14 05:20:32

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

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

I don't see the rest of your code, but I would try create a much smaller number of Event arg classes. Instead create a few that are similar to each other in terms of data contained and/or the way you handle them later and add a field that will tell you what exact type of event occured (probably you should use an enum).

Ideally you would not only make this constructor much more readable, but also the way the messages are handled (group messages that are handled in a similar way in a single event handler)

唠甜嗑 2024-07-14 05:20:32

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

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

很难选择其他方法来解决这个问题,因为架构的其余部分(对我来说)是未知的。

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

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

Perhaps instead of having a different class for each message, use a flag that identifies the message.

That would drastically reduce the number of messages you have and increase maintainability. My guess is that most of the message classes have about zero difference.

It's hard to pick an additional way of attacking this because the rest of the architecture is unknown (to me).

If you look at Windows, for example, it doesn't natively know how to handle each message that might be thrown about. Instead, the underlying message handlers register callback functions with the main thread.

You might take a similiar approach. Each message class would need to know how to handle itself and could register itself with the larger application. This should greatly simplify the code and get rid of the tight coupling.

灼痛 2024-07-14 05:20:32

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

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

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

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

Obviously you are in need of a dispatching mechanism: depending on the event you receive, you want to execute different code.

You seem to be using the type system to identify the events, while it's actually meant to support polymorphism. As Chris Lively suggests, you could just as well (without abusing the type system) use an enumerate to identify the messages.

Or you can embrace the power of the type system, and create a Registry object where every type of event is registered (by a static instance, a config file or whatsoever). Then you could use the Chain of Responsibility pattern to find the proper handler. Either the handler does the handling itself, or it may be a Factory, creating an object that handles the event.

The latter method looks a bit underspecified and overengineered, but in the case of 99 event types (already), it seems appropriate to me.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文