Quando usi l'ereditarietà per riutilizzare il codice, trovi troppo complicato che ingoia i vantaggi del riutilizzo?

8

Ho fatto il codice per circa 8 anni, tuttavia trovo che l'ereditarietà sia troppo flessibile e talvolta ti confonde totalmente con il codice che hai scritto. Un esempio più semplice potrebbe essere:

abstract class AClass {
    protected void method1() {
        if(check()) {
            do1();
        } else {
            do2();
        }
    }
    protected abstract void do1();
    protected abstract void do2();
}

L'intenzione della classe è che le persone possano implementare do1 () e do2 () in modo che possa essere fatta qualche ulteriore logica, tuttavia a volte le persone decidono di sovraccaricare method1 (), e quindi le cose si complicano immediatamente.

Trovo solo nel modello di strategia, il codice viene riutilizzato bene attraverso l'ereditarietà, nella maggior parte dei casi il progettista della classe base conosce molto bene le sue sottoclassi e quell'eredità è totalmente opzionale.

Ho una classe che è ereditata da 4 classi - una IoHandler, ed è sottoclassi per lato server, lato client, server periferico, server di origine, e inizia a farmi impazzire. Sono sempre stato nel refactoring del codice, sono sempre uscito con idee che penso avrebbero funzionato e quindi non sono state provate. Si dice che il cervello umano possa contenere solo 7 informazioni una volta, ne porto troppe?

    
posta tactoth 26.05.2011 - 09:25
fonte

6 risposte

2

Quando continui a ripetere tali schemi, non sembrano più così complicati.

Inoltre, devi conoscere le classi base prima di ereditarle. E l'ereditarietà non sarà facoltativa fintanto che il codice comune viene riutilizzato. Prendi qualsiasi altro modello come fabbrica astratta / composito - quell'eredità in realtà serve a uno scopo.

Regola del pollice: non usare l'ereditarietà solo per tenere insieme un mucchio di classi. Usalo ovunque abbia senso. Nell'esempio forzato, l'utente della classe figlio che sta sovraccaricando il metodo1 deve avere qualche motivo serio per farlo. (OTOH, ho anche voluto un modo per prevenire il sovraccarico di alcune funzioni da solo - per evitare questo). E prima di usare quella lezione per bambini, dovresti sapere anche di questa stranezza. Ma un modello di strategia pura non lo farà - se lo fa, doc molto bene.

Se dai il tuo esempio di codice reale, potrebbe essere che la gente qui può esaminarlo per te.

    
risposta data 26.05.2011 - 09:52
fonte
17

Sì, può essere molto complicato ... e tu sei in buona compagnia.

Joshua Bloch in Java efficace cattura bene alcuni dei problemi dell'ereditarietà e raccomanda di favorire la composizione invece di ereditarietà.

Inheritance is a powerful way to achieve code reuse, but it is not always the best tool for the job. Used inappropriately, it leads to fragile software. It is safe to use inheritance within a package, where the subclass and the superclass implementation are under the control of the same programmers. It is also safe to use inheritance when extending classes specifically designed and documented for extension (Item 15). Inheriting from ordinary concrete classes across package boundaries, however, is dangerous. As a reminder, this book uses the word “inheritance” to mean implementation inheritance (when one class extends another). The problems discussed in this item do not apply to interface inheritance (when a class implements an interface or where one interface extends another).

Unlike method invocation, inheritance breaks encapsulation. In other words, a subclass depends on the implementation details of its superclass for its proper function. The superclass's implementation may change from release to release, and if it does, the subclass may break, even though its code has not been touched. As a consequence, a subclass must evolve in tandem with its superclass, unless the superclass's authors have designed and documented it specifically for the purpose of being extended.

    
risposta data 26.05.2011 - 11:23
fonte
1

Penso che la tua domanda sia troppo vaga e generale. Quello che descrivi sembra un problema complesso, con o senza ereditarietà. Quindi IMHO è del tutto normale che tu stia lottando con esso.

Naturalmente in alcuni casi l'ereditarietà non è lo strumento migliore per un lavoro (riferimento obbligatorio a "favorire la composizione sull'ereditarietà" ;-). Ma se usato con cura, lo trovo utile. Dalla tua descrizione è difficile decidere se l'ereditarietà sia lo strumento giusto per il tuo caso.

    
risposta data 26.05.2011 - 09:50
fonte
1

se i tuoi metodi sono eccessivamente complessi, fanno piangere le sottoclassi

fai in modo che i tuoi metodi facciano una cosa specifica

    
risposta data 26.05.2011 - 15:28
fonte
1

L'ereditarietà della composizione restringe il vettore del cambiamento.

Immagina che la tua soluzione sia implementata e usi AClass in 300 diversi posti della base di codice, e ora c'è il requisito di cambiare 45 delle chiamate method1 () in modo che venga chiamato un metodo do3 () invece di do2 () .

Potresti implementare do3 () su IAnInterface (speriamo che tutte le implementazioni dell'interfaccia possano gestire facilmente un metodo do3 ()) e creare un BClass che richiama do1 () o do3 () nella chiamata method1 () e cambi il 45 riferimenti sia alla AClass che alla dipendenza iniettata.

ora immagine che implementa questa stessa modifica per do4 (), do5 (), ...

O potresti fare quanto segue ...

interface IFactoryCriteria {
    protected boolean check();
}

public final class DoerFactory() {
    protected final IAnInterfaceThatDoes createDoer( final IFactoryCriteria criteria ) {
        return criteria.check() ? new Doer1() : new Doer2();
    }
}

interface IAnInterfaceThatDoes {
    abstract void do();
}

public final class AClass implements IFactoryCriteria {
    private DoerFactory factory;
    public AClass(DoerFactory factory)
    {
        this.factory = factory;
    }

    protected void method1() {
        this.factory.createDoer( this ).do();
    }

    protected boolean check() {
        return true;//or false
    }
}

Ora hai solo bisogno di implementare un secondo factory che restituisca Doer1 o Doer2 e cambi l'iniezione di fabbrica in 45 posizioni. AClass non cambia, IAnInterface non cambia e puoi gestire facilmente più modifiche come do4 (), do5 ().

    
risposta data 23.06.2011 - 20:26
fonte
0

Perché non fare:

interface IAnInterface {
    abstract void do1();
    abstract void do2();
}

public final class AClass {
    private IAnInterface Dependent;
    public AClass(IAnInterface dependent)
    {
        this.Dependent = dependent;
    }

    protected void method1() {
        if(check()) {
            this.Dependent.do1();
        } else {
            this.Dependent.do2();
        }
    }
}

(Penso che il finale sia l'equivalente di "sealed" in C #, qualcuno per favore correggimi se la sintassi non è corretta).

Iniezione delle dipendenze / IoC significa che puoi ereditare solo i metodi che in realtà vuoi che le persone siano in grado di cambiare, il che credo risolva il tuo problema. Inoltre, non sembra che la classe principale faccia effettivamente qualsiasi cosa do1 e do2, più che delega le chiamate nelle sue sottoclassi in base a una condizione: interrompi la separazione delle preoccupazioni!

    
risposta data 03.06.2011 - 17:30
fonte

Leggi altre domande sui tag