在我的代码上运行FxCop,我收到此警告:
Microsoft.Maintainability: 'FooBar.ctor加上99 9种不同的不同类型 命名空间。重写或重构 减少其类耦合的方法, 或者考虑将方法移到一个方法 其他类型紧密 加上。上面的一个类耦合 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”方法中的代码很少;他们通常将工作交给另一个班级的职能部门。
如何通过较低的耦合使这个类更好?
答案 0 :(得分:15)
让进行工作的课程注册他们感兴趣的事件...... 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);
}
}
}
答案 1 :(得分: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>
输出:
Type 1, Example.Type1EventArgs Type 2, Example.Type2EventArgs No handler defined for event type: EventArgs
如您所见,MessageBroker
不了解任何处理程序,处理程序不了解MessageBroker
。所有映射都在app.config文件中完成,因此如果需要处理新的事件类型,可以将其添加到配置文件中。如果其他团队正在定义事件类型和处理程序,这是特别好的 - 他们可以在dll中编译他们的东西,将其放入部署中,只需添加映射。
字典具有object类型的值而不是MessageHandler<>
,因为实际的处理程序无法强制转换为MessageHandler<EventArgs>
,所以我不得不破解它。我认为解决方案仍然很干净,它可以很好地处理映射错误。请注意,您还需要在此项目中引用Spring.Core.dll。您可以找到图书馆here和文档here。 dependency injection chapter与此相关。另请注意,没有理由需要使用Spring.NET - 这里的重要思想是依赖注入。不知何故,某些东西需要告诉代理将类型a的消息发送到x,并且使用IoC容器进行依赖注入是让代理不了解x的好方法,反之亦然。
与IoC和DI相关的其他一些问题:
答案 2 :(得分:0)
也许不是为每条消息设置不同的类,而是使用标识消息的标志。
这将大大减少您拥有的邮件数量并提高可维护性。我的猜测是大多数消息类的差异都是零。
很难再选择一种攻击方式,因为架构的其余部分是未知的(对我而言)。
例如,如果你看一下Windows,它本身就不知道如何处理可能引发的每条消息。相反,底层消息处理程序向主线程注册回调函数。
您可能采取类似的方法。每个消息类都需要知道如何处理自己,并且可以使用更大的应用程序注册自己。这应该会大大简化代码并消除紧密耦合。
答案 3 :(得分:0)
我没有看到你的其余代码,但我会尝试创建一个少得多的Event arg类。而是根据包含的数据和/或稍后处理它们的方式创建一些彼此相似的字段,并添加一个字段,告诉您事件的确切类型(可能您应该使用枚举)。
理想情况下,您不仅可以使此构造函数更具可读性,还可以处理消息的方式(在单个事件处理程序中以类似方式处理的组消息)
答案 4 :(得分:0)
显然你需要一个调度机制:根据你收到的事件,你想要执行不同的代码。
您似乎正在使用类型系统来识别事件,而它实际上意味着支持多态。正如Chris Lively建议的那样,你也可以(不滥用类型系统)使用枚举来识别消息。
或者您可以拥抱类型系统的强大功能,并创建一个注册表对象,其中注册了每种类型的事件(通过静态实例,配置文件或任何其他方式)。然后,您可以使用“责任链”模式来查找正确的处理程序。处理程序自己处理它,或者它可能是Factory,创建一个处理事件的对象。
后一种方法看起来有点不明确和过度设计,但在99种事件类型(已经)的情况下,对我来说似乎是合适的。