2015-05-21 17 views
10

Ok, struttura di codice domanda:gestore di eventi Registrati specifica sottoclasse

Diciamo che ho una classe, FruitManager, che riceve periodicamente Fruit oggetti da alcuni dati-source. Ho anche alcune altre classi che devono essere notificate quando vengono ricevuti questi oggetti Fruit. Tuttavia, ogni classe è interessata solo a determinati tipi di frutta e ogni frutto ha una logica diversa per come dovrebbe essere gestita. Diciamo per esempio la classe CitrusLogic ha metodi OnFruitReceived(Orange o) e OnFruitReceived(Lemon l), che dovrebbero essere chiamati quando si riceve il rispettivo sottotipo di frutta, ma non ha bisogno di essere notificato di altri frutti.

Esiste un modo per gestire con eleganza questo in C# (presumibilmente con eventi o delegati)? Ovviamente potrei aggiungere solo i gestori di eventi generici OnFruitReceived(Fruit f) e utilizzare le istruzioni if ​​per filtrare sottoclassi indesiderate, ma ciò sembra inelegante. Qualcuno ha un'idea migliore? Grazie!

Modifica: Ho appena trovato generic delegates e sembra che potrebbero essere una buona soluzione. Suona come una buona direzione per andare?

+0

si potrebbe fare Frutta generico - '' Frutta

+0

@ DanielA.White io non sono sicuro di vedere come sarebbe affrontarlo. Potresti elaborare? – thomas88wp

+0

Ho modificato il tuo titolo. Per favore vedi, "[Le domande dovrebbero includere" tag "nei loro titoli?] (Http://meta.stackexchange.com/questions/19190/)", dove il consenso è "no, non dovrebbero". –

risposta

3

Prima di tutto, Unity supporta un sottoinsieme di .NET 3.5 in cui il sottoinsieme specifico dipende dai parametri di costruzione.

Passando alla domanda, il modello di evento generale in C# utilizza i delegati e la parola chiave dell'evento. Poiché si desidera chiamare i gestori solo se il frutto in entrata è compatibile con la definizione del metodo, è possibile utilizzare un dizionario per eseguire la ricerca. Il trucco è di che tipo memorizzare i delegati come. È possibile utilizzare un po 'tipo di magia per farlo funzionare e memorizzare tutto come

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

Questo non è l'ideale, perché ora tutti i gestori sembrano accettare Fruit invece dei tipi più specifici. Questa è solo la rappresentazione interna, tuttavia, le persone pubblicamente aggiungeranno ancora gestori specifici tramite

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

Ciò mantiene l'API pubblica pulita e specifica del tipo.Internamente il delegato deve passare da Action<T> a Action<Fruit>. Per fare ciò, crea un nuovo delegato che acquisisca un Fruit e lo trasformi in un T.

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

Questo non è certo un cast sicuro. Si bloccherà se viene passato qualcosa che non è T (o eredita da T). Questo è il motivo per cui è molto importante archiviare solo internamente e non esposto al di fuori della classe. Memorizza questa funzione sotto la chiave Typetypeof(T) nel dizionario dei gestori.

Successivamente per richiamare l'evento è necessaria una funzione personalizzata. Questa funzione deve richiamare tutti i gestori di eventi dal tipo dell'argomento lungo tutta la catena di ereditarietà ai più generici gestori di Fruit. Ciò consente a una funzione di essere trigger su qualsiasi argomento sottotipo, non solo sul suo tipo specifico. Questo mi sembra il comportamento intuitivo, ma può essere lasciato fuori se lo si desidera.

Infine, è possibile visualizzare un evento normale per consentire l'aggiunta di tutti gli operatori di tipo Fruit nel solito modo.

Di seguito è riportato l'esempio completo. Si noti che l'esempio è abbastanza minimale ed esclude alcuni controlli di sicurezza tipici come il controllo nullo. Esiste anche un potenziale loop infinito se non esiste una catena di ereditarietà da child a parent. Un'attuazione effettiva dovrebbe essere ampliata come si ritiene opportuno. Potrebbe anche utilizzare alcune ottimizzazioni. Soprattutto negli scenari ad alto utilizzo la memorizzazione nella cache delle catene ereditarie potrebbe essere importante.

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

Questo suona come un problema per il Observer pattern. Utilizzando System.Reactive.Linq, abbiamo anche l'accesso alla classe Observable che contiene una serie di metodi di Linq per gli osservatori, tra cui .OfType<>

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

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

Se avete bisogno di aggiungere tutti sovraccarichi esistenti per tipo, come ad esempio tutte le implementazioni di AFruitLogic<TFruit>, è necessario eseguire la scansione del assembly utilizzando riflessione o guardare in varie metodologie IoC come MEF

+0

Questa è una buona soluzione, ma sfortunatamente sto lavorando in Unity con .NET 2.0, quindi non ho accesso a IObservable. – thomas88wp

+0

@ thomas88wp Mi spiace sentirlo, allora. Assicurati di aggiungere tali tag alle tue domande in futuro, ma per motivi di domande future lascerò qui questa risposta. – David

+0

Sì, ho pensato di includerlo nella descrizione (dato che è il mio problema specifico), ma volevo anche che questo thread fosse utile alle persone in generale con la stessa domanda, quindi la tua risposta potrebbe essere utile a qualcun altro. Inoltre, grazie a @Colin sopra per aver sottolineato che Unity supporta un sottoinsieme di .NET 3.5 e tutti i 2.0 (ancora nessun '' IObservable'', comunque). – thomas88wp

0

io suggerirei catena di design pattern responsabilità. È possibile creare una catena di FruitHandlers. Una volta ricevuto un frutto, passa attraverso questa catena finché un conduttore non è in grado di elaborare il suo tipo di frutta.

0

In primo luogo, non usare if per instradare il logici. Se si finisce per utilizzare un gestore generico, passare tutti i frutti a tutti i gestori e lasciare filtrare i gestori. Ciò ti farà risparmiare dolore per la manutenzione a lungo termine.

Per quanto riguarda la questione di quello che sarebbe il mezzo più efficiente per il routing della frutta attraverso i gestori, che è una domanda più difficile, perché è altamente dipendente dalla vostra situazione particolare.

Quello che vorrei fare sarebbe quella di creare una facciata di movimentazione di frutta che prende tutte le classi xLogic e ha un certo tipo di metodo di registrazione come

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

// later 
fruitHandlers.Handle(fruit); 

Poi internamente, si può trattare con diverse implementazioni per vedere cosa funziona . Per un esempio, data una definizione gestore della regola come:

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

è possibile creare una tabella di ricerca interno per l'attuazione del gestore frutta

Dictionary<Type, List<IFruitLogic>> fruitHandlers; 

Quando un nuovo gestore viene registrato, si raccolgono il tipo e poi Aggiungilo alla lista. Usa l'elenco per chiamare solo i gestori che contano per quella classe. Questo è un esempio approssimativo. Dal momento che i tuoi gestori potrebbero avere metodi diversi, potresti anche solo passare i metodi da soli.

Nel tuo caso di default si potrebbe anche semplicemente avere

List<FruitLogic> handlers; 

e avere ogni gestore di prendersi cura di essa la propria filtraggio.

L'importante è stabilire un'API che permetta di giocare con i dettagli di implementazione per qualcosa che sia ottimale per il tuo dominio. Misurare le prestazioni di diverse soluzioni in un ambiente realistico è l'unico modo per trovare la soluzione migliore per te.

Si noti che gli esempi di codice non sono necessariamente compilabili, solo esempi.

0

Ovviamente ho potuto solo aggiungere gestori di eventi generici OnFruitReceived (frutta f), e utilizzare se le dichiarazioni di filtrare sottoclassi indesiderati

temo che non sarà possibile trovare un altro modo, o in realtà non troverai un modo 'più breve', quindi ti suggerisco di risparmiare tempo e iniziare a scrivere le tue istruzioni if.

1

Ho utilizzato un aggregatore di eventi generici che potrebbe aiutarti in questo caso.

Il seguente codice non è scritto in .Net2.0 ma è possibile modificarlo facilmente con .Net2.0 eliminando l'uso di alcuni metodi 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); 
    } 
} 

I suoi modelli o qualsiasi altra cosa si desidera pubblicare

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

quindi utilizzarlo come segue

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

quali uscite

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

Nota: La versione di aggregatore evento fornito abov usa il modello di evento debole, quindi è necessario avere un forte riferimento agli abbonati per tenerlo in vita. Se vuoi che sia un riferimento forte, puoi semplicemente convertire il riferimento debole in riferimento forte.

+0

Hey Sriram, grazie per la soluzione dettagliata. Penso che sia abbastanza simile a quello che ho selezionato, e probabilmente funzionerebbe anche per chiunque fosse interessato. Mi piace l'uso dell'altro dei delegati (Action) su un tipo di interfaccia, perché probabilmente avrò una classe logica che gestisce molti tipi di frutta e che potrebbe diventare un po 'macchinosa con l'implementazione di un sacco di interfacce. Comunque, un utile frammento, grazie. – thomas88wp