我在C#解决方案中有一个pub / sub机制用于事件聚合。 虽然理论上发布者和订阅者之间的耦合度很低,但在我看来,消息本身可能会引入紧密耦合。
这是我到目前为止所做的:
public class A : ISubscribe<StartMessage> {
void Handle(StartMessage msg){
...
}
}
public class B {
void PublishStart() {
eventAggregator.Publish(new StartMessage())
...
}
}
这很好,因为它不依赖于魔术字符串,但无论StartMessage
位于哪里(它位于A
,B
或者可能在其自己的类中更明智) A
和B
都取决于StartMessage
。
有没有办法避免这种情况而不诉诸魔法弦?
我考虑使用某些界面,例如消息实现的IMessage
但是所有听众都会收到IMessages
,并且必须过滤if (IMessage is StartMessage)
这样可怕的内容。
修改
一个问题是,通过EventAggregator进行通信的MVVM-ish模式中的ViewModel必须依赖于特定的消息。这意味着在添加新消息时重用这些VM可能很困难。
答案 0 :(得分:1)
更广泛地回答:我认为消息的特定生产者以及特定消费者/订阅者在逻辑上不可避免地严重依赖于他们生成或消费的实际消息实现。
相比之下,消息分发框架当然可以是通用的,除了简单的接口之外不需要任何知识(即队列不透明的订阅者并将不透明的消息传递给它们)。
这允许将消息实现细节的知识限制到实际使用它们的位置。实现此目的的一种方法是将订阅者和消息放在继承层次结构中。每个具体订阅者类型仅涉及特定的消息类型。 只有特定的订阅者类才需要了解该特定的消息实现,并且在该特定的订阅者类中,不需要了解任何其他消息。(这似乎是您的有效关注。)这使得它成为可能。可以扩展现有的消息/订户系统:可以通过实施适当的类来添加消息和相应的订户而无需更改现有代码,并且当添加以前未知的消息(及其订户)时,预先存在的用户代码可以很好地发挥作用。 p>
据我所知,这将涉及运行时类型检查,您似乎很害怕。要使用函数重载是不可能的,因为重载解析是编译时功能,不能用于编译时未知的类型。相反,虚函数调用是运行时功能。但是,如果订阅者从一个基类继承,那么显式运行时检查就可以轻松完成,基类是一个类型参数是消息类型的模板;模板代码实现所有派生订户的消息类型检查。也许这个例子是一个灵感:
//// part of the library code for message passing ////////////////////////////
public interface IMessage{}
public interface ISubscribe
{
void Handle(IMessage msg);
}
/// <summary>
/// A base class for IMessage subscribers who are only
/// interested in a particular message type.
/// The Handle() funciton performs the type check and
/// calls HandleImpl() if and only if the message has the proper type
/// given in the type parameter. Derived
/// subscribers only need to implement HandleImpl().
/// </summary>
/// <typeparam name="MessageT">The message type the derived subscriber
/// is interested in.</typeparam>
public abstract class SubscriberBaseT<MessageT>: ISubscribe
where MessageT: class, IMessage
{
/// <summary>
/// Check whether the message is of the type we are interested in.
/// If yes, call our handling implementation.
/// Note: No knowledge of specific message types or internals.
/// </summary>
/// <param name="msg">The IMessage to check</param>
public void Handle(IMessage msg)
{
var messageTmsg = msg as MessageT;
if( msg != null )
{
HandleImpl(messageTmsg);
}
}
/// <summary>
/// To be implemented by derived classes.
/// Do something with the message type we are concerned about.
/// </summary>
/// <param name="concreteMsg">A message of the type we are
/// interested in.</param>
public abstract void HandleImpl(MessageT concreteMsg);
}
//// user code file1.cs ////////////////////////////
/// <summary>
/// A user defined message
/// </summary>
public class Msg1T: IMessage { /***/ }
/// <summary>
/// A user defined handler interested only in Msg1T messages.
/// Note: No knowledge of other message types.
/// </summary>
public class Msg1SubscrT: SubscriberBaseT<Msg1T>
{
public override void HandleImpl(Msg1T msg)
{
// do something with this particular message
}
}
//// user code file2.cs ////////////////////////////
/// <summary>
/// Another user defined message
/// </summary>
public class Msg2T: IMessage { /***/ }
/// <summary>
/// Another user defined handler,
/// interested only in Msg2T messages
/// </summary>
public class Msg2SubscrT: SubscriberBaseT<Msg2T>
{
public override void HandleImpl(Msg2T msg)
{
// do something with this Msg2T
}
}
//// user code file3.cs ////////////////////////////
// ...
答案 1 :(得分:0)
如果编译时只知道有限数量的不同消息类型,则可以将虚拟调度机制用于IMessage
接口的不同实现:
interface IMessage
{
void CallSubscriberHandle(ISubscribe subscriber);
}
class StartMsg: IMessage
{
public void CallSubscriberHandle(ISubscribe subscr) { subscr.Handle(this); }
}
class ProgressMsg: IMessage
{
public void CallSubscriberHandle(ISubscribe subscr) { subscr.Handle(this); }
}
class EndMsg: IMessage
{
public void CallSubscriberHandle(ISubscribe subscr) { subscr.Handle(this); }
}
此模式根据消息的运行时类型调用不同的Handle()
函数,因为在调用Handle
的代码中,该类型是静态已知的。订户必须为每种消息类型实现一种新方法,因此必须知道这些类型(或者将调用后备)。这是一种比单方法接口更紧密的耦合,但消息仍然只知道一个接口。 (如果你有大量的消息类型或者在编译时不知道这些类型你运气不好,但我没有看到你如何逃脱,没有字符串或其他数据标准。)
所有子程序实现必须实现所有Handle()
重载:
interface ISubscribe
{
void Handle(IMessage msg);
void Handle(StartMsg msg);
//void Handle(ProgressMsg msg); // don't handle this one
void Handle(EndMsg msg);
}
或许有些令人惊讶的是,这段代码会导致每条消息的处理方式不同:
foreach(IMessage msg in msgList)
{
// Use virtual function dispatch in the message instead of
// a switch/case at the message handler
msg.CallSubscriberHandle(msgHandler);
}
为了完整性,这里是一个有效的例子,虽然不是最小的:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace VirtualDispatchTest
{
interface ISubscribe
{
void Handle(IMessage msg);
void Handle(StartMsg msg);
//void Handle(ProgressMsg msg); // don't handle this one
void Handle(EndMsg msg);
}
class MsgHandler: ISubscribe
{
// fall back, for unknown message types
public void Handle(IMessage msg) { Console.Out.WriteLine("I'm not sure what I'm doing right now"); }
public void Handle(StartMsg sm) { Console.Out.WriteLine("Here we go!"); }
// Let's make his message type unknown
//public void Handle(ProgressMsg pm) { Console.Out.WriteLine("Having fun making progress..."); }
public void Handle(EndMsg em) { Console.Out.WriteLine("Bummer, already over."); }
}
interface IMessage
{
void CallSubscriberHandle(ISubscribe subscriber);
}
class StartMsg: IMessage
{
public void CallSubscriberHandle(ISubscribe subscr) { subscr.Handle(this); }
}
class ProgressMsg: IMessage
{
public void CallSubscriberHandle(ISubscribe subscr) { subscr.Handle(this); }
}
class EndMsg: IMessage
{
public void CallSubscriberHandle(ISubscribe subscr) { subscr.Handle(this); }
}
class Program
{
static List<IMessage> msgList = new List<IMessage>();
static MsgHandler msgHandler = new MsgHandler();
static void Main(string[] args)
{
msgList.Add(new StartMsg());
msgList.Add(new ProgressMsg());
msgList.Add(new EndMsg());
msgList.Add(new StartMsg());
msgList.Add(new EndMsg());
foreach(IMessage msg in msgList)
{
// Use virtual function dispatch in the message instead of
// a switch/case at the message handler
msg.CallSubscriberHandle(msgHandler);
}
}
}
}
输出:
Here we go! I'm not sure what I'm doing right now Bummer, already over. Here we go! Bummer, already over.