2015-05-21 8 views
10

Ok, структура кода вопроса:Регистрация обработчика событий для конкретного подкласса

Скажем, у меня есть класс, 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). Вот почему очень важно, что он хранится только внутри, а не снаружи класса. Сохраните эту функцию под ключом Typetypeof(T) в словаре обработчиков.

Для вызова события требуется специальная функция. Эта функция должна вызывать все обработчики событий от типа аргумента вплоть до цепочки наследования до самых общих обработчиков Fruit. Это позволяет запускать функцию и для любых подтипов, а не только для определенного типа. Это кажется мне интуитивным поведением, но при желании его можно оставить без внимания.

И наконец, обычное событие может быть подвергнуто воздействию, чтобы разрешить использование всех обработчиков Fruit, которые будут добавлены обычным способом.

Ниже приведен полный пример. Обратите внимание, что пример довольно минимален и исключает некоторые типичные проверки безопасности, такие как проверка нуля. Существует также потенциальный бесконечный цикл, если нет цепи наследования от child до parent. Фактическая реализация должна быть расширена по мере возможности. Он также может использовать несколько оптимизаций. В частности, в сценариях с высоким уровнем использования кэширование цепей наследования может быть важным.

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, мы также получить доступ к Observable класса, который содержит ряд методов Linq для наблюдателей, в том числе .OfType<>

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

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

Если вам нужно добавить все существующие перегрузки по типу, например, всех реализаций AFruitLogic<TFruit>, вам нужно будет отсканировать сборку с помощью отражения или изучить различные методологии IoC, такие как MEF

+0

Это хорошее решение, но, к сожалению, я работаю в Unity с .NET 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 (Fruit F) обработчик событий, а также использовать, если оператор для фильтрации нежелательных подклассов

Я боюсь, что вы не будете искать другой путь, или на самом деле вы не найдете «более короткий» способ, поэтому я предлагаю сэкономить ваше время и начать вводить ваши инструкции if.

1

Я использую агрегатор событий общего назначения, который мог бы помочь вам здесь.

Следующий код не записан на .Net2.0, но вы можете легко изменить его совместимость с .Net2.0, исключив использование нескольких методов Linq.

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 

Примечание: Версия агрегатор событий при условии, что abov e использует слабый шаблон событий, поэтому вам нужно будет прочно ссылаться на подписчиков, чтобы сохранить его в живых. Если вы хотите, чтобы это была сильная ссылка, вы можете просто преобразовать слабую ссылку в сильную ссылку.

+0

Привет, Шрирам, спасибо за подробное решение. Я думаю, что он очень похож на тот, который я выбрал, и, вероятно, будет работать и для всех, кого это интересует. Мне нравится использование другими пользователями делегатов (Action) над типом интерфейса, потому что у меня, вероятно, будет один логический класс, обрабатывающий множество типов фруктов, и это может стать немного громоздким с внедрением множества интерфейсов. Тем не менее, полезный фрагмент, спасибо. – thomas88wp