OOP e completamente evitando l'ereditarietà dell'implementazione possibile?


20

Sceglierò Java come esempio, la maggior parte delle persone lo sa, anche se ogni altra lingua OO funzionava allo stesso modo.

Java, come molti altri linguaggi, ha ereditarietà di interfaccia e implementazione. Per esempio. una classe Java può ereditare da un'altra e ogni metodo che ha un'implementazione lì (supponendo che il genitore non sia astratto) è ereditato anche lui. Ciò significa che l'interfaccia è ereditata e l'implementazione per questo metodo. Posso sovrascriverlo, ma non è necessario. Se non lo sovrascrivo, ho ereditato l'implementazione.

Tuttavia, la mia classe può anche "ereditare" (non in termini Java) solo un'interfaccia, senza implementazione. In realtà le interfacce sono realmente denominate in questo modo in Java, forniscono l'ereditarietà dell'interfaccia, ma senza ereditare alcuna implementazione, dal momento che tutti i metodi di un'interfaccia non hanno implementazione.

Ora c'era questo article, saying it's better to inherit interfaces than implementations, potresti leggerlo (almeno la prima metà della prima pagina), è piuttosto interessante. Evita problemi come lo fragile base class problem. Fin qui tutto ha molto senso e molte altre cose dette nell'articolo hanno molto senso per me.

Ciò che mi infastidisce su questo, è che l'ereditarietà dell'implementazione significa il riutilizzo del codice , una delle proprietà più importanti dei linguaggi OO. Ora se Java non ha classi (come James Gosling, il padrino di Java ha desiderato in base a questo articolo), risolve tutti i problemi di ereditarietà dell'implementazione, ma come renderebbe possibile il riutilizzo del codice possibile?

E.g. se ho una classe Car e Car ha un metodo move(), che fa muovere la macchina. Ora posso sottoclasse auto per diversi tipi di auto, che sono tutte auto, ma sono tutte versioni specializzate di Car. Alcuni possono muoversi in un modo diverso, questi devono sovrascrivere move() in ogni caso, ma la maggior parte manterrebbe semplicemente la mossa ereditata, mentre si muovono allo stesso modo proprio come l'auto genitore astratto. Ora supponiamo per un secondo che ci siano solo interfacce in Java, solo le interfacce possono ereditare l'una dall'altra, una classe può implementare interfacce, ma tutte le classi sono sempre definitive, quindi nessuna classe può ereditare da un'altra classe.

Come eviterai che quando hai una macchina di interfaccia e cento classi di automobili, devi implementare un metodo move() identico per ognuno di essi? Quali concetti per il riutilizzo del codice diverso dall'ereditarietà dell'implementazione esistono nel mondo OO?

Alcune lingue hanno Mixins. Mixin è la risposta alla mia domanda? Ho letto di loro, ma non riesco davvero a immaginare come potrebbero funzionare i Mixin in un mondo Java e se possono davvero risolvere il problema qui.

Un'altra idea era che esiste una classe che implementa solo l'interfaccia Car, chiamiamola AbstractCar e implementa il metodo move(). Ora anche altre auto implementano l'interfaccia Car, internamente creano un'istanza di AbstractCar e implementano il loro metodo move() chiamando move() sulla loro auto astratta interna. Ma questo non sprecare risorse per niente (un metodo che chiama solo un altro metodo - ok, JIT potrebbe inline il codice, ma ancora) e usando memoria extra per mantenere gli oggetti interni, non avresti nemmeno bisogno di ereditarietà dell'implementazione? (Dopo tutto ogni oggetto ha bisogno di più memoria che la semplice somma dei dati incapsulati) Inoltre non è imbarazzante per un programmatore di scrivere i metodi fittizi come

public void move() { 
    abstractCarObject.move(); 
} 

?

Chiunque può immaginare un'idea migliore su come evitare l'ereditarietà dell'implementazione ed essere ancora in grado di riutilizzare il codice in modo semplice?

  0

io per primo non mi piace chiamarlo "interfaccia eredità", ma "implementazione di interfaccia". Questa è una cosa che mi piace molto dal linguaggio Java, due concetti diversi nominati di conseguenza. 11 ott. 092009-10-11 21:40:20

11

Risposta breve: Sì, è possibile. Ma bisogna farlo apposta e non per caso (usando finale, astratto e il design con l'ereditarietà in mente, ecc)

Risposta lunga:

Beh, eredità non è in realtà per "riutilizzo del codice ", è per la" specializzazione "di classe, penso che sia un'interpretazione errata.

Per esempio è una pessima idea creare una pila da un vettore, solo perché sono simili. Oppure le proprietà di HashTable solo perché memorizzano i valori. Vedi [Efficace].

Il "riutilizzo del codice" era più una "visione aziendale" delle caratteristiche OO, il che significa che gli oggetti erano facilmente distribuibili tra i nodi; e erano portatili e non avevano i problemi della precedente generazione di linguaggi di programmazione. Questo è stato dimostrato per metà rigore. Ora abbiamo librerie che possono essere facilmente distribuite; per esempio in Java i file jar possono essere utilizzati in qualsiasi progetto risparmiando migliaia di ore di sviluppo. OO ha ancora qualche problema con la portabilità e cose del genere, questo è il motivo per cui ora i WebServices sono così popolari (come prima era CORBA) ma quello è un altro thread.

Questo è un aspetto del "riutilizzo del codice". L'altro è efficacemente, quello che ha a che fare con la programmazione.Ma in questo caso non è solo per "salvare" linee di codice e creare mostri fragili, ma progettare con l'ereditarietà in mente. Questo è l'articolo 17 nel libro precedentemente menzionato; Elemento 17: progettare e documentare per ereditarietà o altrimenti vietarlo. Vedere [Efficace]

Naturalmente potresti avere una classe di auto e tonnellate di sottoclassi. E sì, l'approccio che citi sull'interfaccia Car, AbstractCar e CarImplementation è un modo corretto di procedere.

Definisci il "contratto" che l'auto dovrebbe rispettare e dire che questi sono i metodi che mi aspetterei di avere quando parliamo di automobili. L'auto astratta che ha la funzionalità di base che ogni macchina, ma lasciando e documentando i metodi che le sottoclassi sono responsabili di gestire. In Java lo fai segnando il metodo come astratto.

Quando si procede in questo modo, non c'è un problema con la classe "fragile" (o almeno il progettista è consapevole o la minaccia) e le sottoclassi completano solo le parti che il progettista consente loro.

L'ereditarietà è più per "specializzare" le classi, nello stesso modo un Truck è una versione specializzata di Car e MosterTruck una versione specializzata di Truck.

Non crea sanse per creare una sottoclasse "ComputerMouse" da un'Auto solo perché ha una Ruota (rotella di scorrimento) come un'auto, si muove e ha una ruota sotto solo per salvare le linee di codice. Appartiene a un dominio diverso e verrà utilizzato per altri scopi.

Il modo per impedire l'ereditarietà di "implementazione" è nel linguaggio di programmazione sin dall'inizio, è necessario utilizzare la parola chiave final sulla dichiarazione della classe e in questo modo si stanno proibendo sottoclassi.

La sottoclasse non è male se è fatta apposta. Se è fatto male, può diventare un incubo. Direi che dovresti iniziare come privato e "definitivo" il più possibile e se necessario rendere le cose più pubbliche ed estendibili. Questo è anche ampiamente spiegato nella presentazione "Come progettare buone API e perché è importante" Vedi [Buona API]

Continua a leggere gli articoli e con il tempo e la pratica (e tanta pazienza) questa cosa verrà più chiara. Anche se a volte hai solo bisogno di fare il lavoro e copiare/incollare del codice: P. Questo è ok, a patto che tu provi a farlo bene prima.

Ecco i riferimenti sia da Joshua Bloch (già lavorando a Sun alla base di Java ora lavora per Google)


[Attivo] Effective Java. Sicuramente il miglior libro di java che un principiante non dovrebbe imparare, capire e praticare. A deve avere.

Effective Java


[Buona API] Presentazione che parla sul design di API, riutilizzabilità e argomenti correlati. È un po 'lungo ma vale ogni minuto.

How To Design A Good API and Why it Matters

saluti.


Aggiornamento: dai un'occhiata al minuto 42 del collegamento video che ti ho inviato.Parla di questo argomento:

"Quando hai due classi in un'API pubblica e pensi di farne una sottoclasse di un'altra, come Foo è una sottoclasse di Bar, chiedi a te stesso, è Every Foo a Bar ?. .. "

E nel minuto precedente parla di" riutilizzo del codice "mentre si parla di TimeTask.

  0

Seriamente, non riesco a vedere come un autocarro dovrebbe ereditare da un'auto :) 11 ott. 092009-10-11 21:55:47

+2

Forse Camion e Automobili dovrebbero ereditare dall'Automobile. :-) 18 feb. 102010-02-18 22:05:38


4

È anche possibile utilizzare la composizione e il modello di strategia. link text

public class Car 
{ 
    private ICar _car; 

    public void Move() { 
    _car.Move(); 
    } 
} 

Questo è molto più flessibile rispetto all'utilizzo di comportamento in base ereditarietà in quanto consente di modificare a runtime, sostituendo nuovi tipi di auto, come richiesto.


2

È possibile utilizzare composition. Nel tuo esempio, un oggetto Car potrebbe contenere un altro oggetto chiamato Drivetrain. Il metodo move() della macchina potrebbe semplicemente chiamare il metodo drive() del suo drivetrain. La classe Drivetrain potrebbe, a sua volta, contenere oggetti come Engine, Transmission, Wheels, ecc. Se hai strutturato la tua gerarchia di classi in questo modo, potresti facilmente creare auto che si muovono in modi diversi componendole con combinazioni diverse delle parti più semplici (es. riutilizzare il codice).


0

È necessario leggere i modelli di progettazione. Scoprirai che le interfacce sono fondamentali per molti tipi di modelli di progettazione utili. Ad esempio, l'astrazione di diversi tipi di protocolli di rete avrà la stessa interfaccia (per il software che la chiama) ma un piccolo riutilizzo del codice a causa di comportamenti diversi di ciascun tipo di protocollo.

Per alcuni algoritmi è l'apertura dell'occhio nel mostrare come mettere insieme una miriade di elementi di una programmazione per svolgere un compito utile. I Pattern di progettazione fanno lo stesso per gli oggetti. Mostra come combinare gli oggetti in un modo per eseguire un'attività utile.

Design Patterns by the Gang of Four


2

Per rendere mixins/composizione più facile, dare un'occhiata alle mie annotazioni e annotazione Processore:

http://code.google.com/p/javadude/wiki/Annotations

In particolare, il mixins esempio:

http://code.google.com/p/javadude/wiki/AnnotationsMixinExample

Nota che attualmente non funziona se l'interf assi/tipi delegati ad avere metodi parametrizzati (o tipi parametrizzati sui metodi). Ci sto lavorando ...

  0

Questa è una cosa piuttosto interessante, ci sei. Darei un'occhiata dettagliata a questo :) 23 set. 082008-09-23 23:23:50


8

Il problema con la maggior parte degli esempi di ereditarietà sono esempi in cui la persona utilizza l'ereditarietà in modo errato, non un errore dell'ereditarietà di astrarre correttamente.

Nell'articolo in cui hai pubblicato un collegamento, l'autore mostra la "frattura" dell'ereditarietà usando Stack e ArrayList. L'esempio è imperfetto perché lo Stack non è un ArrayList e pertanto l'ereditarietà non deve essere utilizzata. L'esempio è difettoso come String extending Character o PointXY extending Number.

Prima di estendere la classe, è necessario eseguire sempre il test "is_a". Dal momento che non puoi dire che Every Stack è una ArrayList senza essere in qualche modo sbagliata, allora non dovresti metterla in conto.

Il contratto per Stack è diverso dal contratto per ArrayList (o List) e stack non deve ereditare metodi a cui non interessa (come get (int i) e add()). Infatti Stack dovrebbe essere un'interfaccia con metodi quali:

interface Stack<T> { 
    public void push(T object); 
    public T pop(); 
    public void clear(); 
    public int size(); 
} 

Una classe come ArrayListStack potrebbe implementare l'interfaccia Stack, e dal fatto che la composizione caso d'uso (avente un ArrayList interno) e non ereditarietà.

L'ereditarietà non è male, l'ereditarietà errata è negativa.


0

L'ereditarietà non è necessaria per un linguaggio orientato agli oggetti.

Considerate Javascript, che è ancora più orientato agli oggetti di Java, probabilmente. Non ci sono classi, solo oggetti. Il codice viene riutilizzato aggiungendo metodi esistenti a un oggetto. Un oggetto Javascript è essenzialmente una mappa di nomi di funzioni (e dati), in cui il contenuto iniziale della mappa viene stabilito da un prototipo e nuove voci possono essere aggiunte a una determinata istanza al volo.


2

È divertente rispondere alla mia domanda, ma ecco qualcosa che ho trovato interessante: Sather.

È un linguaggio di programmazione senza ereditarietà di implementazione! Conosce le interfacce (chiamate classi astratte senza implementazione o dati incapsulati) e le interfacce possono ereditarsi l'una dall'altra (in realtà supportano persino l'ereditarietà multipla!), Ma una classe può solo implementare interfacce (classi astratte, quante ne vuole), non può ereditare da un'altra classe. Può tuttavia "includere" un'altra classe. Questo è piuttosto un concetto delegato. Le classi incluse devono essere istanziate nel costruttore della tua classe e distrutte quando la tua classe viene distrutta. A meno che non si sovrascrivano i metodi che hanno, la classe eredita anche la loro interfaccia, ma non il loro codice. Invece vengono creati metodi che inoltrano semplicemente le chiamate al metodo al metodo ugualmente denominato dell'oggetto incluso. La differenza tra gli oggetti inclusi e gli oggetti appena incapsulati è che non devi creare la delega in avanti tu stesso e non esistono come oggetti indipendenti che puoi passare in giro, fanno parte del tuo oggetto e vivono e muoiono insieme al tuo oggetto (o più tecnicamente parlato: la memoria per il tuo oggetto e tutti quelli inclusi sono creati con una singola chiamata alloc, lo stesso blocco di memoria, devi solo iniziarli nella tua chiamata al costruttore, mentre quando usi i veri delegati, ognuno di questi oggetti causa una propria chiamata alloc, ha un proprio blocco di memoria e vive completamente indipendentemente dal proprio oggetto).

La lingua non è così bello, ma mi piace l'idea dietro di esso :-)