2015-05-21 11 views
10

Mi capita spesso trovo a voler scrivere definizioni di classi generiche di formaEvitare tipi generici di forma Foo <ActualType estende Foo <ActualType>>

public class Foo<ActualType extends Foo<ActualType>> 

Ad esempio, in una configurazione come questa:

public interface ChangeHandler<SourceType> { 
    public void onChange(SourceType source); 
} 


public class Foo<ActualType extends Foo<ActualType>> { 

    private final List<ChangeHandler<ActualType>> handlers = new ArrayList<>(); 

    public void addChangeHandler(ChangeHandler<ActualType> handler) { 
     handlers.add(handler); 
    } 

    @SuppressWarnings("unchecked") 
    protected void reportChange() { 
     for (ChangeHandler<ActualType> handler: handlers) 
      handler.onChange((ActualType) this); 
    } 
} 


public class Bar extends Foo<Bar> { 
    // things happen in here that call super.reportChange(); 
} 


public static void main(String[] args) throws IOException { 

    Bar bar = new Bar(); 
    bar.addChangeHandler(new ChangeHandler<Bar>() { 

     @Override 
     public void onChange(Bar source) { 
      // Do something with the changed object 
     } 
    }); 
} 

L'evento di cambiamento qui è solo un esempio. Questo è più di un problema generale che sto riscontrando ogni volta che vorrei consentire a una super classe di fornire funzionalità che siano "individualizzate" per ogni sottoclasse specifica (non so come esprimerla meglio ... nell'esempio sopra "l'individualizzazione" è il fatto che il ChangeHandler viene chiamato con un oggetto del sottotipo effettivo (Bar) non con il tipo di super-classe (Foo) che chiama il gestore).

In qualche modo questo approccio mi sembra un po 'complicato. E permette in realtà per i potenziali problemi dal momento che nulla mi impedisce di definire poi:

public class Baz extends Foo<Bar> { /* ... */ } 

Esiste un'alternativa più pulita?

il Santo Graal sarebbe un certo parametro di tipo che viene sempre definita per contenere la classe corrente, come una versione statica di this.getClass() che mi avrebbe permesso di scrivere qualcosa di simile a questo, invece:

public class Foo { 

    private final List<ChangeHandler<this.Class>> handlers = new ArrayList<>(); 

    public void addChangeHandler(ChangeHandler<this.Class> handler) { 
     handlers.add(handler); 
    } 

    protected void reportChange() { 
     for (ChangeHandler<this.Class> handler: handlers) 
      handler.onChange(this); 
    } 
} 

Dove this.Class sarebbe essere uguale a Bar per le classi di tipo Bar.

+0

Questa è la versione di Java del [CRTP] (https://stackoverflow.com/questions/4173254/what-is-the-curiously-recurring-template-pattern-crtp). È piuttosto tipico. – dhke

+0

Il Santo Graal sarebbe bello ma non esiste (ancora). Sembra difficile da controllare staticamente a causa dell'ereditarietà. – Radiodef

+0

Non potresti fare 'Foo >', sostituendo tutte le occorrenze di 'ActualType' con' T'? Rimuovi la necessità di 'ActualType'. Non sono sicuro se questo è ciò che intendevi per "più pulito" –

risposta

0

È un problema veramente astratto. A mio parere, la risposta breve a "come rendere questo più pulito" è: utilizzare solo generici dove è necessario.

public class List<T extends List<T>> 

Che cosa sta cercando di esprimere (sostituito)? Una lista che consente solo di tenere (T si estende) altre liste che mantengono Ts (Lista) che, come sappiamo da prima, sono liste che permettono solo di tenere ... e così via. Un po 'circolare, non vedo come si finirebbe con qualcosa del genere?

public interface ChangeHandler<SourceType> { 
    public void onChange(SourceType source); 
} 

Perché vuoi utilizzare i generici qui? Se si desidera disporre di un gestore di modifiche che può gestire diversi tipi di risorse, è possibile creare una super classe da cui ereditano tutte le origini effettive o creare un'interfaccia implementata dalle origini. Così puoi specificare esattamente cosa viene esposto dalle fonti. In alternativa, la fonte può creare un oggetto sorgente quando notifica, invece di passare "questo" (quindi è più simile a un messaggio). Ad esempio:

public interface ChangeHandler { 
    public void onChange(Source source); 
} 

public abstract class Source { 
    private List<ChangeHandler> handlers; 
    protected int nr; 
    public Source(int nr) { 
     this.nr = nr; 
    } 
    public abstract Something getSomething(); 
    public String toString() { 
     return "SRC" + nr; 
    } 
    ... 
    private notify(int index) { 
     handlers.get(i).onChange(this); 
    } 
} 

public class Foo extends Source { 
    public Foo(int nr) { 
     super(nr); 
    } 
    public String toString() { 
     return super.toString() + "Foo"; 
    } 
    public Something getSomething() { 
     return new Something(); 
    } 
} 

Non hai mai bisogno di trasmettere ... o tu? Non sono sicuro di aver capito il problema.

+0

Diciamo che il tuo 'Foo' ha qualche attributo (' title', per esempio) che 'Source' non ha, ti piacerebbe essere in grado di scrivere un' ChangeHandler' che è passato a un 'source' di tipo' Foo' , quindi può fare cose come 'myLabel.setText (source.getTitle());'. Nella tua proposta, 'source' (all'interno di' onChange') sarebbe di tipo 'Source', cioè non avrebbe un titolo. Pertanto, dovresti scrivere cose come 'myLabel.setText (((Foo) source) .getTitle());' ovunque. E tecnicamente dal momento che ora puoi passare questo 'ChangeHandler' ad altri' Sources' devi prima assicurarti che 'source' sia di tipo' Foo'. –

+0

@MarkusA. Se vuoi avere il titolo nelle fonti allora dovresti avere 'abstract abstract String getTitle();' nella classe Source. Ma immagino tu stia guardando qualcosa in cui non tutte le fonti hanno il titolo e se hai bisogno di cast vorresti rilevare gli errori già al momento della compilazione, se possibile? – maraca

+0

Proprio così! In molti casi, infatti, "Source" può essere così generico da non dover nemmeno sapere quali sono le sue sottoclassi potenziali. Soprattutto se fa parte di una biblioteca. Prendi la classe 'Foo' nella mia domanda, per esempio. Potresti immaginare di rinominarlo in "AbstractChangeReportingObject" e incollarlo in una libreria per fornire facilmente la gestione del gestore delle modifiche per qualsiasi potenziale sottoclasse, qualunque esse siano. Ma i gestori di modifiche gradirebbero ancora ricevere una fonte del tipo di oggetto che stanno monitorando come 'AbstractChangeReportingObject' sarà abbastanza inutile ... –

0

Si consiglia di utilizzare semplicemente <This> per rappresentare il "tipo di auto". Non c'è bisogno di essere vincolato, dal momento che sembra complicato, non fornisce l'intenzione e non può comunque forzare il vincolo.

public class Foo<This> { 

    private final List<ChangeHandler<This>> handlers = new ArrayList<>(); 

    public void addChangeHandler(ChangeHandler<This> handler) { 
     handlers.add(handler); 
    } 

    @SuppressWarnings("unchecked") 
    protected void reportChange() { 
     for (ChangeHandler<This> handler: handlers) 
      handler.onChange((This)this); 
    } 
} 

Avviso cast (This)this.

Vedi anche Java generics: Use this type as return type?

Problemi correlati