2012-02-03 13 views
5

Date un'occhiata a questo codice (da here)Ereditarietà e casting: è buono questo java?

abstract class EntityA { 
    AssocA myA; 
    abstract void meet(); 
} 

abstract class AssocA { 
    int something; 
    abstract void greet(); 
} 

class AssocAConcrete extends AssocA { 
    void greet() { 
     System.out.println("hello"); 
    } 
    void salute() { 
     System.out.println("I am saluting.") 
    } 
} 

class EntityAConcrete extends EntityA { 
    void meet() { 
     System.out.println("I am about to meet someone"); 
     ((AssocAConcrete)myA).salute(); 
    } 
} 

Ci sono due alberi di ereditarietà in parallelo, per una classe genitore e una classe associata. Il problema è con la linea 23:

((AssocAConcrete)myA).salute();

è un dolore e non ho questo genere di cose in tutto il mio codice. Anche se questa linea è parte dell'implementazione concreta di Entity, devo ricordarmi che voglio utilizzare l'implementazione concreta di AssocA, AssocAConcrete.

C'è qualche tipo di annotazione per dichiarare tale relazione? O c'è un modo migliore, più colloquiale in Java per esprimere questo design? Grazie!


Questo è in risposta a @ Dave, perché voglio mettere un po 'di codice in ...

Interessante! Quindi l'invocazione sarebbe simile a questa:

AssocAConcrete myAssoc = new Assoca(); 
EnitityA<T extends AssocA> myEntity = new EntityA<AssocAConcrete>(); 
myEntity.setAssoc(myAssoc); 
myAssoc.salute(); 

Sì? È davvero fantastico. Penso che lo userò!

+0

Se scrivi 'AssocAConcrete myA;' invece di 'AssocA myA;' (riga 2), lo risolverebbe. Ma ciò interromperà il resto del codice? –

+0

Sì, l'idea generale è che potrebbero esserci diverse varianti di AssocAConcrete ei client di EntityA vorrebbero non essere dipendenti da questo. Si noti che il codice che utilizza solo AssocA e EntityA non conosce o menziona mai l'una o l'altra classe Concrete. E solo le due classi concrete "A" conoscono i dettagli dell'implementazione. – pitosalas

risposta

8

vorrei che questo sia molto più ordinato utilizzando farmaci generici ...

abstract class EntityA<T extends AssocA> { 

    // Basically, this means myA is at least an AssocA but possibly more... 
    T myA; 
    abstract void meet(); 
} 

abstract class AssocA { 
    int something; 
    abstract void greet(); 
} 

class AssocAConcrete extends AssocA { 
    void greet() { 
     System.out.println("hello"); 
    } 
    void salute() { 
     System.out.println("I am saluting."); 
    } 
} 

class EntityAConcrete extends EntityA<AssocAConcrete> { 
    void meet() { 
     System.out.println("I am about to meet someone"); 
     myA.salute(); 
    } 
} 

Oltre a evitare la fusione, questo rende anche molto più facile da aggiungere diverse funzionalità nei vostri AssocA implementazioni. Ci dovrebbe sempre essere un modo per fare le cose senza usare implementazioni fittizie (cioè metodi che lanciano semplicemente "NotImplementedException") o casting. Anche se non è sempre facile o vale il tempo di refactoring per farlo. In altre parole, nessuno ti biasimerà per il casting (beh ... forse alcune persone lo faranno ma non puoi accontentare tutti).

EDIT (Note sulla creazione di un'istanza):

Dai commenti @pitosalas' sotto ...

//Won't work...can't call 'new' on abstract class AssocA 
AssocAConcrete myAssoc = new Assoca(); 

//Instead, do this... 
AssocAConcrete myAssoc = new AssocAConcrete(); 

e poi ....

// Again, won't work. T is only declaring the type inside your class/method. 
// When using it to declare a variable, you have to say EXACTLY what you're making, 
// or at least something as exact as the methods you're trying to invoke 
EnitityA<T extends AssocA> myEntity = new EntityA<AssocAConcrete>(); 

//Instead do this... 
EnitityA<AssocAConcrete> myEntity = new EntityAConcrete(); 

// Or this... 
EntityAConcrete myEntity = new EntityAConcrete(); 

E allora questo dovrebbe be good ...

// Assuming this is defined as `public void setAssoc(T newAssoc) {this.myA = newAssoc;}` 
myEntity.setAssoc(myAssoc); 
myAssoc.salute(); 
+1

Vorrei poterti dare +2. Questo è esattamente ciò che i generici sono per; e non capisco perché alcune persone si allontanino da loro. Oh, e io sono una delle persone che DOVREBBE incolpare te per aver usato il casting per risolvere un problema che è meglio risolvere con i generici. –

+0

Ah interessante. E li istanziavo in questo modo? – pitosalas

+0

Come si modificherebbe l'istanza. Dov'è il codice che stai attualmente utilizzando per creare un'istanza? Potresti aggiornare la tua domanda per avere un esempio? – Dave

3

Sembra sospettoso per me. Non c'è nulla di terribile nel casting, ma in questo caso è possibile risolvere il problema portando il metodo salute in AssocA. Sottoclassi di AssocA possono fornire le loro implementazioni; questo fa parte del beneficio dell'eredità.

Quello che state facendo ora sta dicendo tutte le istanze EntityA hanno un'istanza AssocA, ma poi nel tuo metodo meet che, fondamentalmente, costringono l'istanza AssocA ad essere un'istanza AssocAConcrete. Questa è la parte che è sospetta; perché esiste AssocA se hai davvero bisogno di un AssocAConcrete.

Un'altra opzione (basata sui commenti) è di invocare salute nel metodo greet. In questo modo, la sottoclasse specifica ha specificato il comportamento greet, definito nella superclasse e fa ciò che vuole. In questo caso, salute potrebbe diventare privato o protetto. Un'altra implementazione può facilmente fare qualcosa di diverso, come runLikeHell.

+0

Questo tipo di accoppiamento logico tra famiglie di classi è molto comune. Purtroppo, il sistema di tipi non supporta l'espressione. – ewernli

+0

@nwgotcodes - non proprio la stessa cosa. Voglio nascondere la conoscenza dell'implementazione delle due classi concrete "A", in modo che ognuna sia a conoscenza l'una dell'altra, ma solo AssocAConcrete sa come salutare. AssocXConcrete potrebbe non sapere come farlo. – pitosalas

+0

quindi invoca 'salute' nel metodo di saluto. – hvgotcodes

0

Il problema delle gerarchie di classi parallele è molto reale e fa davvero schifo. L'accoppiamento logico che AssocAConcrete accompagna sempre con EntityAConcrete non può essere espresso con il sistema di tipi.

Non è possibile specializzare il tipo di myA in EntityAConcrete in AssocAConcrete, senza nasconderlo da una superclasse. Penso che il lavoro più vicino a cui è stato rivolto sia stato "Family polymorphism", ma non è mainstream.

0

Se si dispone di una gran parte del codice in cui si utilizza il riferimento "myA" si potrebbe dichiarare un altro riferimento del genere:

public AssocAConcrete myAConcrete = (AssocAConcrete)myA; 

Ora è possibile utilizzare il nuovo riferimento myAConcrete e accedere alle funzioni del AssocAConcrete Classe.

Se è necessario eseguire questa operazione molto simile a hvgotcodes menzionati, è consigliabile considerare di spostare il metodo fino alla classe AssocA.