Sta usando i generici per indicare che qualche classe / metodo è a conoscenza di una buona idea del tipo?

2

Userò Java per la dimostrazione di codice / idea.

Immagina che sia necessario definire due interfacce: Observer e Observable ( observer Reticolo ). È una buona idea avere due interfacce generiche, quindi non c'è alcuna restrizione su ciò che viene osservato , chi lo sta osservando (sta addirittura usando i generici su questo?).

Java ha una Observable classe concreta (non finale) e un'interfaccia Observer . Devi delegare a gestire gli osservatori e a notificarli durante l'aggiornamento alla classe Observer concreta. Questo è un modo per implementare il modello. Non so se sia l'approccio migliore (probabilmente lo è), ma per il gusto di questo esempio supponiamo che ogni classe / tipo che vuole essere osservabile, debba implementare l'interfaccia Observable below e gestire tutto da solo.

Non dovremmo dimenticare che l'interfaccia / oggetto Observer e Observable originali sono stati implementati in JDK1.0, quando i generici non erano disponibili.

Ora la domanda: è una buona idea dichiarare semplicemente un tipo generico in un'interfaccia per indicare che la classe di implementazione deve essere consapevole di un tipo specifico? Oppure è un modo totalmente sbagliato di usare i generici?

See:

public interface Observable<T> {
    // Only observers who are aware of type T are allowed.
    // But should really Observable be concerned about it?
    public void registerObserver(Observer<? extends T> o);
} 

// The T here denotes that implementing class will be aware of a type called T,
// (not that it can operate on a range of types).
public interface Observer<T> {
    // Observer is not aware of Observable interface directly. But it knows about T.
    public void update(T arg);
}

VS

public interface Observable {
    // Is it an observer? then it is allowed. That's all we need to know.
    public void registerObserver(Observer o);

    // Observers will be passed the instance of observable, 
    // then they call this method to fetch arg.
    // something like:
    // for(Observer o: this.observers) o.update(this);
    public <T> T getArg();
} 

public interface Observer {
    // Pass me an Observable who returns T and i'll do the rest!
    public void update(Observable o);
}

Sento che i farmaci generici sono completamente sbagliati.

P.S: Confrontando questo modo di implementare il modello di osservatore (chiedendo ad ogni tipo di re-implementare queste interfacce) rispetto alla soluzione java originale (delegazione) ci rendiamo conto che non è davvero una buona idea. Causa un sacco di codice duplicato. Ma aiuta a capire meglio i farmaci generici.

Aggiornamento Il vero problema con java.util.Observer è notifyObservers(Object arg) [link a doc] dove arg è lanciato in fase di runtime dagli osservatori. Come possiamo evitare questo tipo di cast?

    
posta hkoosha 03.01.2015 - 23:09
fonte

2 risposte

4

... la risposta ristretta alla tua domanda è che, quando trasmetti un oggetto in Object, devi necessariamente ridiscutere il tipo specifico più tardi, a meno che non usi i metodi Object come toString, o altrimenti esegui un controllo di fine del controllo del tipo del compilatore con l'introspezione di runtime (ad es. Reflection).

Quando elimini un oggetto (ad esempio something.setAsObject("a string") ), perdi informazioni sul tipo di quell'oggetto. Puoi recuperarlo in vari modi, ma i generici sono come portare il tipo su per il compilatore per sapere cosa aspettarsi in seguito, e non richiede il downcast a ciò che era prima.

...

I generici di Java aprono la porta per il polimorfismo parametrico , che è un tipo diverso di polimorfismo di cui di solito parliamo in OO . Permette di creare tipi "contenitore" o "wrapper" che aggiungono comportamenti indipendenti dai tipi sottostanti. Come dice wikipedia, "una funzione o un tipo di dati possono essere scritti genericamente in modo che possa gestire i valori in modo identico senza dipendere dal loro tipo"

Ad esempio, per un List<T> , quello che <T> dice è che un elenco funziona allo stesso modo indipendentemente dal fatto che sia un List<String> o un List<Integer> . Puoi eseguire le stesse operazioni e quando hai finito puoi recuperare oggetti di qualsiasi tipo che contiene l'elenco.

Quindi, in un modo divertente, i generici permettono a un tipo di essere inconsapevole del tipo sottostante. Un List<T> non sa, non gli interessa, che tipo contiene. Un Observer<T> non sa, non gli interessa, che tipo sta osservando. Fornisce solo una certa semantica su quel tipo.

Ciò consente al codice di evitare un downcast. I downcast sono visti come non validi in OO, perché aumentano la possibilità di un'eccezione di classe di esecuzione runtime - si perde il vantaggio del controllo dei tipi da parte del compilatore.

Nel tuo esempio, rendendo generiche le tue interfacce Observer sta dicendo: "Non mi importa di quale tipo sto osservando, ho intenzione di fornire questo meccanismo di Observer su di esso." Sembra un'applicazione aptica di generici.

(Per quel che vale, quando si entra nel business della combinazione di polimorfismo parametrico (ovvero tipi di contenitori che aggiungono comportamenti) con interfacce concatenate o fluenti, è possibile creare alcune API programmatiche davvero potenti ed eleganti.)

    
risposta data 04.01.2015 - 16:27
fonte
0

C'è un problema molto importante con il design che hai presentato, il che è causato da un errore nel modo in cui vengono implementati i java generics: una singola classe può implementare solo una singola versione di un'interfaccia generica. Penso che solo la possibilità di osservare i cambiamenti in un singolo tipo di oggetto sarebbe eccessivamente restrittivo, e quindi di solito non userei questa implementazione.

Inoltre, non vedo alcun valore nell'interfaccia Observable; per osservare utilmente un oggetto, l'osservatore deve conoscerne il tipo, quindi l'interfaccia non è necessaria. Definirei semplicemente un'interfaccia Observer per tipo che può essere osservata. È un po 'di codice boilerplate, ma solo una manciata di righe per tipo. L'implementazione della registrazione e la rimozione degli osservatori è probabilmente più ampia.

    
risposta data 04.01.2015 - 11:57
fonte

Leggi altre domande sui tag