2015-05-21 38 views
10

好,代码结构问题:注册事件处理程序的具体子类

比方说,我有一个类,FruitManager,定期从一些数据源接收Fruit对象。我还有一些其他类需要在收到这些Fruit对象时得到通知。然而,每个课程只对某些类型的水果感兴趣,每种水果对于如何处理它们都有不同的逻辑。举例来说,CitrusLogic类别具有方法OnFruitReceived(Orange o)OnFruitReceived(Lemon l),当收到相应的水果子类型时应调用该方法,但不需要通知其他水果。

有没有一种方法来优雅地处理这在C#(大概与事件或代表)?很明显,我可以添加通用的OnFruitReceived(Fruit f)事件处理程序,并使用if语句来过滤不需要的子类,但这看起来不够雅致。有没有人有更好的主意?谢谢!

编辑:我刚刚发现generic delegates,他们似乎可以是一个很好的解决方案。这听起来像是一个好的方向吗?

+0

你可以做水果一般 - '水果' –

+0

@ DanielA.White我不知道我怎么看这将解决这个问题。你能详细说明一下吗? – thomas88wp

+0

我编辑了你的标题。请参阅:“[应该在其标题中包含”标签“](http://meta.stackexchange.com/questions/19190/)”,其中的共识是“不,他们不应该”。 –

回答

3

首先,Unity支持.NET 3.5的一个子集,其中特定的子集取决于您的构建参数。

转到您的问题,C#中的一般事件模式是使用委托和事件关键字。由于您只希望处理程序在传入的水果与其方法定义兼容时才被调用,因此可以使用字典来完成查找。技巧是将代表存储为什么类型。你可以用一点神奇的类型,使工作和所有都

Dictionary<Type, Action<Fruit>> handlers = new Dictionary<Type, Action<Fruit>>(); 

这是不理想的,因为现在所有的处理程序似乎接受Fruit,而不是更具体的类型。然而,这只是内部表示,公众人员仍然会通过添加特定处理程序

public void RegisterHandler<T>(Action<T> handler) where T : Fruit 

这会保持公共API的清洁和类型特定。代表在内部需要将Action<T>更改为Action<Fruit>。为此,创建一个新的代理,它接受Fruit并将其转换为T

Action<Fruit> wrapper = fruit => handler(fruit as T); 

这当然不是安全的演员。如果它传递的不是T(或从T继承),它会崩溃。这就是为什么它非常重要,它只存储在内部而不暴露在课堂之外。将该函数存储在处理程序字典中的Type密钥typeof(T)下。

在调用事件之前需要一个自定义函数。这个函数需要调用所有事件处理程序,从继承链上的参数类型到最通用的Fruit处理程序。这允许函数在任何子类型参数上触发,而不仅仅是它的特定类型。这对我来说似乎是直觉行为,但如果需要可以省略。

最后,可以暴露正常事件以允许以通常方式添加捕获所有处理程序Fruit

下面是完整的例子。请注意,该示例非常简单,并排除了一些典型的安全检查,如空检查。如果没有从childparent的继承链,那么也存在潜在的无限循环。应视实际情况扩大实际执行情况。它也可以使用一些优化。特别是在高使用情况下,缓存继承链可能很重要。

public class Fruit { } 

class FruitHandlers 
{ 
    private Dictionary<Type, Action<Fruit>> handlers = new Dictionary<Type, Action<Fruit>>(); 

    public event Action<Fruit> FruitAdded 
    { 
     add 
     { 
      handlers[typeof(Fruit)] += value; 
     } 
     remove 
     { 
      handlers[typeof(Fruit)] -= value; 
     } 
    } 

    public FruitHandlers() 
    { 
     handlers = new Dictionary<Type, Action<Fruit>>(); 
     handlers.Add(typeof(Fruit), null); 
    } 

    static IEnumerable<Type> GetInheritanceChain(Type child, Type parent) 
    { 
     for (Type type = child; type != parent; type = type.BaseType) 
     { 
      yield return type; 
     } 
     yield return parent; 
    } 

    public void RegisterHandler<T>(Action<T> handler) where T : Fruit 
    { 
     Type type = typeof(T); 
     Action<Fruit> wrapper = fruit => handler(fruit as T); 

     if (handlers.ContainsKey(type)) 
     { 
      handlers[type] += wrapper; 
     } 
     else 
     { 
      handlers.Add(type, wrapper); 
     } 
    } 

    private void InvokeFruitAdded(Fruit fruit) 
    { 
     foreach (var type in GetInheritanceChain(fruit.GetType(), typeof(Fruit))) 
     { 
      if (handlers.ContainsKey(type) && handlers[type] != null) 
      { 
       handlers[type].Invoke(fruit); 
      } 
     } 
    } 
} 
1

这听起来像是Observer pattern的问题。使用System.Reactive.Linq,我们还可以访问其中包含了一系列的LINQ的方法观察员Observable类,包括.OfType<>

fruitSource.OfType<CitrusFruit>.Subscribe(new CitrusLogic()); 
fruitSource.OfType<LemonFruit>.Subscribe(new LemonLogic()); 

... 
public class Ciruslogic : IObersver<CitrusFruit> 
{ ... } 

如果您需要添加的类型所有现有的过载,如AFruitLogic<TFruit>所有实现,您需要使用反射来扫描装配或查看各种IoC方法,如MEF

+0

这是一个很好的解决方案,但不幸的是我在Unity 2.0中工作,所以我无法访问IObservable。 – thomas88wp

+0

@ thomas88wp对不起,那么。请务必在将来为您的问题添加此类标签,但为了将来的查询,我会在此留下答案。 – David

+0

是的,我想过在描述中包含它(因为这是我的具体问题),但我也希望这个线程对于一般人有相同问题的帮助,所以你的答案可能会被别人找到用处。此外,感谢上面的@Colin指出,Unity支持.NET 3.5的一个子集,并且都支持2.0(尽管如此,仍然没有“IObservable”)。 – thomas88wp

0

我会建议责任链设计模式。你可以创建一个FruitHandlers链。一旦收到水果,它就会穿过这条链,直到处理者能够处理其水果类型。

0

首先,不要使用if语句来路由你的逻辑。如果最终使用通用处理程序,则将所有处理程序传递给所有处理程序,并让处理程序进行过滤。这将为您节省长期的维护痛苦。

至于什么是通过处理程序路由水果的最有效方法的问题,这是一个更难的问题,因为它高度依赖于您的特定情况。

我会做的是创建一个水果处理的门面是通吃XLogic类,并有一些像

IFruitHandlers fruitHandlers; 
fruitHandlers.Register(new CitrusLogic()) // Or some good DI way of doing this 

// later 
fruitHandlers.Handle(fruit); 

类型的注册方法。然后在内部,可以应对不同的实现方式,看看是什么在起作用。举一个例子,给出一个逻辑处理器的定义,如:

public class FruitLogic<T> where T:Fruit {} 

你可以在水果处理程序实现

Dictionary<Type, List<IFruitLogic>> fruitHandlers; 

内部创建一个查找表,当一个新的处理程序注册,你收获的类型,然后将它添加到列表中。使用该列表仅调用对该类很重要的处理程序。这是一个粗略的例子。既然你的处理程序可能有不同的方法,你也可以直接传递这些方法。

在默认的情况下,你也可以只是

List<FruitLogic> handlers; 

,并让每个处理器照顾它自己的过滤。

重要的是要建立一个API,使其能够灵活地处理最适合您的域的实现细节。在现实环境中测量不同解决方案的性能是寻找最佳解决方案的唯一途径。

请注意,代码示例不一定是可编译的,只是示例。

0

很显然,我可以只添加通用OnFruitReceived(水果F)事件处理程序,并使用if语句来过滤不必要的子类

恐怕你不会找到另一种方式,或其实你不会找到一个'短'的方式,所以我建议节省你的时间,并开始输入你的if语句。

1

我一直在使用一个通用的事件聚合器,可以帮助你在这里。

下面的代码不是写在.Net2.0中,但你可以很容易地通过消除几个Linq方法来修改它与.Net2.0兼容。

namespace Eventing 
{ 
    public class EventAggregator : IEventAggregator 
    { 
     private readonly Dictionary<Type, List<WeakReference>> eventSubscriberLists = 
      new Dictionary<Type, List<WeakReference>>(); 
     private readonly object padLock = new object(); 

     public void Subscribe(object subscriber) 
     { 
      Type type = subscriber.GetType(); 
      var subscriberTypes = GetSubscriberInterfaces(type) 
       .ToArray(); 
      if (!subscriberTypes.Any()) 
      { 
       throw new ArgumentException("subscriber doesn't implement ISubscriber<>"); 
      } 

      lock (padLock) 
      { 
       var weakReference = new WeakReference(subscriber); 
       foreach (var subscriberType in subscriberTypes) 
       { 
        var subscribers = GetSubscribers(subscriberType); 
        subscribers.Add(weakReference); 
       } 
      } 
     } 

     public void Unsubscribe(object subscriber) 
     { 
      Type type = subscriber.GetType(); 
      var subscriberTypes = GetSubscriberInterfaces(type); 

      lock (padLock) 
      { 
       foreach (var subscriberType in subscriberTypes) 
       { 
        var subscribers = GetSubscribers(subscriberType); 
        subscribers.RemoveAll(x => x.IsAlive && object.ReferenceEquals(x.Target, subscriber)); 
       } 
      } 
     } 

     public void Publish<TEvent>(TEvent eventToPublish) 
     { 
      var subscriberType = typeof(ISubscriber<>).MakeGenericType(typeof(TEvent)); 
      var subscribers = GetSubscribers(subscriberType); 
      List<WeakReference> subscribersToRemove = new List<WeakReference>(); 

      WeakReference[] subscribersArray; 
      lock (padLock) 
      { 
       subscribersArray = subscribers.ToArray(); 
      } 

      foreach (var weakSubscriber in subscribersArray) 
      { 
       ISubscriber<TEvent> subscriber = (ISubscriber<TEvent>)weakSubscriber.Target; 
       if (subscriber != null) 
       { 
        subscriber.OnEvent(eventToPublish); 
       } 
       else 
       { 
        subscribersToRemove.Add(weakSubscriber); 
       } 
      } 
      if (subscribersToRemove.Any()) 
      { 
       lock (padLock) 
       { 
        foreach (var remove in subscribersToRemove) 
         subscribers.Remove(remove); 
       } 
      } 
     } 

     private List<WeakReference> GetSubscribers(Type subscriberType) 
     { 
      List<WeakReference> subscribers; 
      lock (padLock) 
      { 
       var found = eventSubscriberLists.TryGetValue(subscriberType, out subscribers); 
       if (!found) 
       { 
        subscribers = new List<WeakReference>(); 
        eventSubscriberLists.Add(subscriberType, subscribers); 
       } 
      } 
      return subscribers; 
     } 

     private IEnumerable<Type> GetSubscriberInterfaces(Type subscriberType) 
     { 
      return subscriberType 
       .GetInterfaces() 
       .Where(i => i.IsGenericType && 
        i.GetGenericTypeDefinition() == typeof(ISubscriber<>)); 
     } 
    } 

    public interface IEventAggregator 
    { 
     void Subscribe(object subscriber); 
     void Unsubscribe(object subscriber); 
     void Publish<TEvent>(TEvent eventToPublish); 
    } 

    public interface ISubscriber<in T> 
    { 
     void OnEvent(T e); 
    } 
} 

你的模型或任何你想要发布

public class Fruit 
{ 

} 

class Orange : Fruit 
{ 
} 

class Apple : Fruit 
{ 
} 

class Lemon : Fruit 
{ 
} 

//Class which handles citrus events 
class CitrusLogic : ISubscriber<Orange>, ISubscriber<Lemon> 
{ 
    void ISubscriber<Orange>.OnEvent(Orange e) 
    { 
     Console.WriteLine(string.Format("Orange event fired: From {0}", this.GetType().Name)); 
    } 

    void ISubscriber<Lemon>.OnEvent(Lemon e) 
    { 
     Console.WriteLine(string.Format("Lemon event fired: From {0}", this.GetType().Name)); 
    } 
} 

//Class which handles Apple events 
class AppleLogic : ISubscriber<Apple> 
{ 
    void ISubscriber<Apple>.OnEvent(Apple e) 
    { 
     Console.WriteLine(string.Format("Apple event fired: From {0}", this.GetType().Name)); 
    } 
} 

然后使用如下

void Main() 
{ 
    EventAggregator aggregator = new EventAggregator(); 

    CitrusLogic cl =new CitrusLogic(); 
    AppleLogic al =new AppleLogic(); 
    aggregator.Subscribe(cl); 
    aggregator.Subscribe(al); 
    //... 

    aggregator.Publish(new Apple()); 
    aggregator.Publish(new Lemon()); 
    aggregator.Publish(new Orange()); 
} 

,输出

Apple event fired: From AppleLogic 
Lemon event fired: From CitrusLogic 
Orange event fired: From CitrusLogic 

注:事件聚合的版本提供e使用弱事件模式,因此您必须强烈参考订阅者才能保持活动状态。如果你想要它是强大的参考,你可以简单地将弱参考转换为强参考。

+0

嘿Sriram,谢谢你的详细解决方案。我认为它与我选择的非常相似,并且可能对任何感兴趣的人都有效。我喜欢另一种使用接口类型的委托(Action),因为我可能有一个逻辑类处理大量的水果类型,并且在实现一堆接口时可能会有点麻烦。仍然是一个有用的代码片段,谢谢。 – thomas88wp

相关问题