Possiamo dire "Se una classe genitore non compare mai nei miei codici tranne nella sua classe figlio, dovrebbe essere la composizione invece dell'ereditarietà"?

4

Ho letto alcuni post su "composizione sull'ereditarietà", "dove usare la composizione / ereditarietà", "Is-una relazione ..." o "principio di sostituzione di Liskov" per qualche tempo, ma non sono sicuro di ottenere la giusta idea di "composizione sull'ereditarietà".

In alternativa, nella mia esperienza, sembra solo una semplice regola d'oro per verificare se dovremmo usare la composizione o l'ereditarietà:

If a parent class never appears in my codes except in its child class, it should be composition instead of inheritance

per esempio, supponiamo di avere una classe genitore Fruit:

public class Fruit{
    public String printName(){
        System.out.println("Fruit");
    }
}

e classe figlio Orange:

public class Orange extends Fruit{
    public String printName(){
        System.out.println("Orange");
    }
}

e Grape:

public class Grape extends Fruit{
    public String printName(){
        System.out.println("Grape");
    }
}

Se ho davvero bisogno di ereditarietà, nel mio codice sorgente dovrebbe esserci qualcosa di simile

Vector<Fruit> fruitList;

,

Fruit fruit=new Orange();

o

((Fruit)f).printName();

che ha bisogno di "Fruit" per compilare, quindi senza contare Orange e Grape (classe child), se i miei codici sorgente non hanno "Fruit", o se i miei codici possono essere compilati senza "Fruit", Orange e Grape non dovrebbero essere esteso da "Frutto" e quindi dall'eredità errata, è vero?

    
posta ggrr 22.03.2016 - 03:04
fonte

6 risposte

9

Tecnicamente, nel tuo esempio, non stai prendendo una decisione tra la composizione e l'ereditarietà, ma tra l'uso dell'ereditarietà o non l'ereditarietà. In altre parole, creando due classi Grape e Orange non correlate. Ecco perché stai ricevendo alcuni commenti pedanti. Tuttavia, la tua regola empirica è appropriata e si applicherebbe anche se aggiungessi composizione al mix. È certamente una regola empirica più semplice da applicare rispetto alla vaga "preferisci la composizione sull'eredità".

Sfortunatamente, la progettazione orientata agli oggetti ha una grande tradizione nell'usare l'ereditarietà per modellare le relazioni is-a reali, indipendentemente dal fatto che tale relazione sia effettivamente utile nel codice. Ha anche una grande tradizione di creare gerarchie di ereditarietà con un focus laser solo sulle classi stesse, senza considerare il codice chiamante su cui dovresti pensare per applicare la tua regola empirica. Ciò porta molte persone a pensare che hanno bisogno della classe genitore Fruit quando in realtà non lo fanno. Per esperienza, so che otterrete un pushback se provate ad insegnare alle persone in questo modo, ma ciò non significa che non dovreste usare la regola per voi stessi.

    
risposta data 22.03.2016 - 04:56
fonte
1

Tutto ciò che l'ereditarietà può fare la composizione può fare. E la composizione può fare più cose. Niente nella vita è gratis. La composizione ti costa digitando e leggendo.

Se una classe genitore non compare mai nel tuo altro codice tranne nella sua classe figlia, dovresti riscrivere l'altro codice perché non è ciò per cui l'ereditarietà o composizione sono.

Se ho un'anatra che può ciarlare, un cane che può abbaiare e un gatto che può miagolare posso modellarli come animali che possono parlare. La mia classe di animali potrebbe usare l'ereditarietà o la composizione. Potrebbe essere solo un'interfaccia. Il resto del tuo codice non ha bisogno di sapere. Non dovrebbe volerlo sapere. È molto più facile pensare ad un'anatra come qualcosa che puoi dire di parlare. Che cessi quando gli dici di farlo non sono affari tuoi.

Codice in questo modo e il tuo codice esterno sarà blisamente inconsapevole di dove Fruit è un involucro, un genitore, un'interfaccia, astratta o concreta.

In che modo quindi dovresti decidere tra ereditarietà e compostezza? L'ereditarietà è l'opzione pigro fragile. Pigro è buono Fragile è cattivo Scegli il tuo veleno.

Personalmente, preferisco l'ereditarietà solo quando creo le classi interne anonimi.

Inoltre, perché i tuoi metodi printName() restituiscono una stringa? Sembra sciocco e inaspettato. Soprattutto quando non c'è ritorno.

Anche questo sembra un esempio stupido, ma non posso sopportare di vedere usare codice e codice costruttivo costretti a vivere insieme. Quindi ecco il mio inutile esempio di composizione.

public class Grape {
  Fruit fruit;
  public Grape(Fruit fruit) {
    this.fruit = fruit;
  }
  public void printName(){
    fruit.printName();
  }
}

f = new Grape(new Fruit());
f.printName()

Qui vedete Grape che delega printName a Fruit. Grape ha la possibilità di intervenire e prevenire o aumentare la chiamata di fruit.printName() ma finora non è stata codificata per farlo.

Potresti pensare, potrei farlo con l'ereditarietà e avresti ragione. Quello che non puoi fare è questo:

public class Grape {
  Fruit fruit;
  public Grape(Fruit fruit) {
    this.fruit = fruit;
  }      
  public void printName(){
    fruit.printName();
  }
  public void setFruit(Fruit fruit){
    this.fruit = fruit;
  }
}

f = new Grape(new Fruit());
f.printName()
f.setFruit(new RottenFruit());
f.printName()

Prova a fare quel trucco con l'ereditarietà.

    
risposta data 22.03.2016 - 04:35
fonte
1

Anche se non usi mai "Fruit" direttamente nella tua applicazione, se hai diversi metodi identici tra le tue effettive classi Orange, Grape, Banana, Dragonfruit, (etc etc), allora una classe base astratta per Fruit per contenerle sarebbe essere perfettamente appropriato per soddisfare le preoccupazioni di ASCIUTTO. L'esempio seguente è C #, ma si spera che abbia senso anche per Java / etc.

public abstract class Fruit
{
    public readonly int CaloriesPerServing;

    public Fruit(int caloriesPerServing)
    {
        CaloriesPerServing = caloriesPerServing;
    }

    public void SayFruit()
    {
        Console.WriteLine("fruit!");
    }

    public string GetCalorieStatementLabel()
    {
        return "This item contains " + CaloriesPerServing + " calories per serving.";
    }
}

public class Orange : Fruit
{
    public Orange() : base(200) { }
}

Liskov stesso vuol dire che se hai una collezione di oggetti, tutti ereditati da Fruit, chiamando un metodo comune, o usando ognuno di essi come parametro in un nuovo metodo, avranno risultati comuni. Non necessariamente identici, ma solo logicamente comuni o correlati. Ad esempio, ecco una violazione di Liskov, perché gestisce un certo tipo di Fruit in modo molto diverso:

static void AddfFruitToFruitSalad(FruitSalad fruitSalad, Fruit fruit)
{
    if (fruit is Tomato)
        throw new InvalidFruitException("ARE YE MAD?!?!");

    fruitSalad.Add(fruit);
}
    
risposta data 22.03.2016 - 13:39
fonte
0

Composizione

La composizione è quando implementi un oggetto come figlio di un altro oggetto. Quindi, usando il tuo esempio di Fruit come una classe base, dovresti creare un oggetto Grape in questo modo:

public class Grape {
  internal Fruit fruit;
  public Grape() {
    fruit = new Fruit();
    // set fruit's values
  }
  public void printName(){
    System.out.println(fruit.name);
  }
}

Quando usi la composizione in questo modo, non puoi passare l'intero oggetto come se fosse un Fruit (sto parlando in termini di C # come non hai specificato una lingua).

Inheritance

L'ereditarietà si ha quando si implementa un oggetto come versione estesa di un altro oggetto.

public class Grape : Fruit {
  public Grape() : base() {
    // Set some values including fruit object values
  }
  public void printName(){
    System.out.println(base.name);
  }
}

Uno (di molti) vantaggi dell'utilizzo dell'ereditarietà in questo modo è che l'oggetto Grape può essere passato e utilizzato ovunque venga utilizzato un oggetto Fruit .

    
risposta data 22.03.2016 - 04:23
fonte
0

La risposta breve è "Sì". Questa è una buona regola per vivere in generale, tenendo presente che altre persone in futuro potrebbero voler Vector<Fruit> fruitList; e se non hai la classe Fruit perché non ne hai avuto bisogno, potrebbe essere un problema. (Molti linguaggi moderni hanno modi per creare retroattivamente l'interfaccia Fruit e affermare la conformità di alcune classi ad esso.)

Ci sono due ragioni per usare l'ereditarietà, perché vuoi la conformità del tipo (o la sostituibilità del tipo) o perché vuoi riutilizzare il codice. Se gli unici oggetti nel tuo sistema che fanno riferimento alla classe base sono le sottoclassi, allora stai solo sottoclassi per il riutilizzo del codice e dovresti prendere in seria considerazione il refactoring della classe base fuori dall'esistenza.

Il tuo esempio particolare è anche peggio perché non stai neanche cercando di riutilizzare il codice nelle sottoclassi, quindi sembra che tu stia usando la sottoclasse per la conformità del tipo, ma non lo stai nemmeno facendo.

    
risposta data 22.03.2016 - 12:39
fonte
0

Penso che ti stia concentrando troppo sulle regole tecniche. L'is-a è una buona linea guida, ma quello che dovresti davvero chiedere è se la frutta abbia un significato nel tuo dominio o meno. Certo, un'arancia è frutto e una mela è frutto, ma te ne importa? Se si utilizzano istanze di frutta come elementi di manipolazione, potrebbe essere inutile classificarli come frutti. Se si ha a che fare con un'aliquota che si applica alla frutta e una che si applica alle verdure, si ha una buona ragione per classificarle come frutta. Ci dovrebbe essere una certa dipendenza dalla frutta nel dominio del problema. In caso contrario, l'ereditarietà potrebbe essere comunque buona, ma potresti anche scendere apple e orange dall'oggetto o JuggleItem se ciò si applica.

La tua regola è un controllo post-fatto: sai solo quando hai finito. Ma avresti potuto sapere in anticipo se avessi fatto le mie domande.

    
risposta data 22.03.2016 - 21:02
fonte

Leggi altre domande sui tag