Quando si costruisce un pattern di osservatore (pattern di eventi) è meglio usare le classi o un enum per gli eventi?

6

Quando si costruisce un pattern Observer, è necessario definire i propri eventi che vengono trasmessi e osservati. È meglio definire una classe astratta Event con sottoclassi multiple per ogni evento, o definire un singolo enum, in cui ogni evento è elencato nell'enum?

opzione 1:

public abstract class Event { }
public UserInputEvent extends Event {}
public NetwortkEvent extends Event{}

opzione 2:

public enum Event {
 USER_INPUT_EVENT,
 NETWORK_EVENT
}

Ho visto esempi di entrambi:

Mi sto orientando verso l'opzione 2. Gran parte della mia decisione si basa sul fatto che sto lavorando in java, quindi potrebbe essere diverso per le diverse lingue:

  1. l'opzione 1 richiede un sacco di controlli if X instanceOf Y che sembrano brutti.
  2. l'opzione 2 consentirà di pulire le istruzioni switch
  3. l'opzione 2 elencherà tutti gli eventi possibili in un'unica posizione. So che la maggior parte degli IDE può mostrare tutte le sottoclassi di una classe, ma questo è meno ovvio.
  4. l'opzione 1 non limita l'aggiunta di più sottoclassi. Se stai scrivendo una libreria, qualsiasi utente della libreria può sottoclasse Event e ingombra il codice con più eventi. l'opzione 2 corregge il numero di eventi fino a quando qualcuno non aggiorna effettivamente l'enumerazione
  5. l'opzione 1 consente gli eventi sub-sub, che possono essere buoni o cattivi. (ad esempio public TouchUserInputEvent extends UserInputEvent {}
  6. l'opzione 1 ti consente di allegare ulteriori metadati all'evento. IMO questo può portare a codice ingombra se il tuo obiettivo è semplice.

come esempio:

public UserInputEvent extends Event {
    int xLocation;
    int yLocation;
}

UPDATE: Alla fine ho optato per l'opzione 1. Avevo bisogno di includere dati aggiuntivi nell'evento e usare una classe era l'unico modo per andare. Plush @whatsisname aveva ragione, dovendo recuperare i dati dell'evento aggiornato sarebbe il callback dell'inferno.

    
posta tir38 08.08.2017 - 16:42
fonte

3 risposte

7

Il mio voto va all'opzione 1.

Perché: esistono no clean switch . Quando hai enumerati e inizi a cambiarli, apri le porte di un posto molto buio. Prima c'è un interruttore, poi ce n'è un altro, e un altro, ...

E molto presto, la tua base di codice ha un sacco di conoscenza attorno al significato delle diverse costanti enum sparse in milioni di posti.

C'è solo un buon uno per gli switch - che anche "perché l'opzione 1 è migliore". Vedete, in buona OOP, che non chiedete a un oggetto sul suo stato di prendere una decisione al riguardo. Piuttosto, invochi metodi su quell'oggetto e lascia che faccia ciò che deve essere fatto.

In questo senso: spendi le tue energie pensando all'interfaccia comune della tua classe astratta. E poi non ti importa di quale "caso" hai - perché invochi sempre sempre i metodi stesso - ma lo fai su oggetti diversi.

E venendo da lì: a volte è necessario switch - perché si dispone di una factory che ha bisogno di scoprire quale classe specifica deve essere istanziata e restituita al codice client.

Per farla breve: la risposta OOP a questo tipo di problemi è il polimorfismo e molto spesso: una macchina a stati. Le istruzioni switch d'altra parte non hanno (t tanto) posto in OOP. Il punto è: usando il polimorfismo, è possibile scrivere codice che è più facile da mantenere e migliorare. Poiché tale codice supporta benissimo il principio aperto / chiuso . Le dichiarazioni switch sparse no. Al contrario: spesso ti obbligano a modificare il codice quando cerchi di migliorare la funzionalità.

[per la cronaca: non sto parlando di abbinamenti fantasiosi del pattern il più possibile in lingue più nuove - sarebbe una discussione separata]

    
risposta data 08.08.2017 - 21:10
fonte
3

L'opzione 2 è un modo in stile C di fare le cose. È fantastico, quando si programma in C.

Tuttavia, stai usando Java, che si aspetta un design orientato agli oggetti. Se si guarda a come altri sistemi GUI, che hanno gestori di eventi come quelli che si stanno immaginando, molto spesso sottoclasse varie classi di eventi per soddisfare diversi tipi di eventi. Guarda la classe EventArgs e i suoi figli in C # per un esempio.

Riguarda 1 aspettarsi che molti controlli instanceof non vengano riscontrati con un design sensibile. Invece, si usano le funzioni di callback (o delegati in C # o cose simili in altre lingue), dove il callback sa quale tipo di dati sta arrivando. Un listener di eventi che si aspetta eventi di tastiera ha chiamate a funzioni che vengono fornite esclusivamente da eventi di tastiera. Nessun controllo di instanceof o qualcosa del genere.

option 1 allows you to attach additional meta data to the event. IMO this can lead to cluttered code if your goal is simplicity.

Se pensi che raggruppare bit di dati rilevanti in classi e passare quelli in giro porta a "clutter", avrai qualche sorpresa nel tuo futuro. Basta ricevere la notifica dell'evento, quindi interrogare per ottenere valori aggiornati non è né più pulito né meno ingombrante. Se lo fai, non solo devi mantenere la pipeline di notifica degli eventi, ma ora potresti anche dover mantenere metodi di accesso ai dati aggiuntivi. Inoltre, hai introdotto condizioni di gara che possono causare alcuni errori incredibilmente odiosi e difficili da debug.

Se ancora non credi alle mie affermazioni, scrivi qualche app C Win32 dove gestisci il message pump, accendi il tuo messaggio in arrivo e controlla e trasmetti i parametri lParam e wParam (Opzione 2), quindi reimplementa l'app in C # , che avvolge il message pump nel fornire un'interfaccia come l'Opzione 1, e vedere quale risulta essere più pulito e semplice.

    
risposta data 08.08.2017 - 22:29
fonte
0

Se stai programmando in java, userei

public interface Event {...}

perché in Java entrambi, un enum e una classe possono implementare un'interfaccia.

Quale da usare è un dettaglio di implementazione che può essere modificato in qualsiasi momento.

Anche le enumerazioni Java possono avere metodi. Sfortunatamente Enum non è espandibile in modo che un enum non ereditato non aggiunga altri valori costanti.

Quindi presumo che finirai con l'argomentazione di @GhostCat e userai una classe.

I utilizzare questo approccio di interfaccia per gestire i metadati delle immagini. Ho implementato metadati EXIF come enumerazione (come exif può essere rappresentato come ordinale) e metadati Xmp come classe (è rappresentato da stringhe non ordinali) entrambi implementando la stessa interfaccia

    
risposta data 09.08.2017 - 15:44
fonte

Leggi altre domande sui tag