Molte classi piccole rispetto all'eredità logica (ma) complessa

24

Mi chiedo cosa c'è di meglio in termini di buon design OOP, codice pulito, flessibilità ed evitando odori di codice in futuro. Situazione dell'immagine, in cui sono presenti molti oggetti molto simili che è necessario rappresentare come classi. Queste classi non hanno alcuna funzionalità specifica, solo classi di dati e sono diverse solo per nome (e contesto) Esempio:

Class A
{
  String name;
  string description;
}

Class B
{
  String name;
  String count;
  String description;
}

Class C
{
  String name;
  String count;
  String description;
  String imageUrl;
}

Class D
{
  String name;
  String count;
}

Class E
{
  String name;
  String count;
  String imageUrl;
  String age;
}

Sarebbe meglio tenerli in classi separate, ottenere una "migliore" leggibilità, ma con molte ripetizioni di codice, o sarebbe meglio usare l'ereditarietà per essere più DRY?

Usando l'ereditarietà, ti ritroverai con qualcosa del genere, ma i nomi delle classi perderanno il significato contestuale (perché is-a, non ha-a):

Class A
{
  String name;
  String description;
}

Class B : A
{
  String count;
}

Class C : B
{
  String imageUrl;
}

Class D : C
{
  String age;
}

So che l'ereditarietà non è e non dovrebbe essere usata per il riutilizzo del codice, ma non vedo in nessun altro modo possibile come ridurre la ripetizione del codice in questo caso.

    
posta user969153 14.02.2013 - 21:19
fonte

5 risposte

58

La regola generale indica "Preferisca la delega sull'ereditarietà", non "evita l'ereditarietà". Se gli oggetti hanno una relazione logica e una B può essere usata ovunque sia prevista una A, è buona pratica usare l'ereditarietà. Tuttavia, se gli oggetti hanno solo campi con lo stesso nome e non hanno alcuna relazione di dominio, non utilizzare l'ereditarietà.

Molto spesso, è utile fare la domanda sulla "ragione del cambiamento" nel processo di progettazione: se la classe A ottiene un campo in più, ti aspetteresti che anche la classe B la ottenga? Se è vero, gli oggetti condividono una relazione e l'ereditarietà è una buona idea. In caso contrario, subisci la piccola ripetizione per mantenere distinti concetti in codice distinto.

    
risposta data 14.02.2013 - 21:22
fonte
18

By using inheritance, you will end up with something like this, but class names will lost it contextual meaning (because of the is-a, not has-a):

Esattamente. Guarda questo, fuori dal contesto:

Class D : C
{
    String age;
}

Quali proprietà ha una D? Puoi ricordare? Cosa succede se complichi ulteriormente la gerarchia:

Class E : B
{
    int numberOfWheels;
}

Class F : E
{
    String favouriteColour;
}

E allora se, orrore degli orrori, vuoi aggiungere un campo imageUrl a F ma non a E? Non puoi derivarlo da C ed E.

Ho visto una situazione reale in cui molti tipi di componenti diversi avevano tutti un nome, un ID e una Descrizione. Ma questo non era per la natura del loro essere componenti, era solo una coincidenza. Ognuno aveva un ID perché erano memorizzati in un database. Il nome e la descrizione erano per la visualizzazione.

I prodotti avevano anche un ID, un nome e una descrizione, per ragioni simili. Ma non erano componenti, né erano componenti prodotti. Non vedresti mai le due cose insieme.

Ma alcuni sviluppatori hanno letto su DRY e hanno deciso di rimuovere ogni accenno di duplicazione dal codice. Quindi ha definito tutto un prodotto e ha eliminato la necessità di definire quei campi. Sono stati definiti nel prodotto e tutto ciò che necessitava di quei campi poteva derivare dal prodotto.

Non posso dire abbastanza strong: Questa non è una buona idea.

DRY non si tratta di rimuovere la necessità di proprietà comuni. Se un oggetto non è un prodotto, non chiamarlo prodotto. Se un prodotto non RICHIEDE una descrizione, per la natura stessa del suo essere un prodotto, non definire la descrizione come una proprietà del prodotto.

È successo, infine, che abbiamo avuto componenti senza descrizioni. Quindi abbiamo iniziato ad avere descrizioni Null in quegli oggetti. Non annullabile. Nullo. Sempre. Per qualsiasi prodotto di tipo X ... o successivo Y ... e N.

Questa è una grave violazione del Principio di sostituzione di Liskov.

DRY significa non metterti mai in una posizione in cui potresti correggere un bug in un posto e dimenticarti di farlo da qualche altra parte. Questo non accadrà perché hai due oggetti con la stessa proprietà. Mai. Quindi non preoccuparti.

Se il contesto delle classi rende ovvio che dovresti, che sarà molto raro, allora e solo allora dovresti considerare una classe genitore comune. Ma, se si tratta di proprietà, considera perché non stai utilizzando un'interfaccia invece.

    
risposta data 14.02.2013 - 22:20
fonte
4

L'ereditarietà è un modo per ottenere un comportamento polimorfico. Se le tue classi non hanno alcun comportamento, l'ereditarietà è inutile. In questo caso, non li considererei nemmeno oggetti / classi, ma strutture.

Se vuoi essere in grado di mettere diverse strutture in diverse parti del tuo comportamento, allora considera le interfacce con i metodi get / set o le proprietà, come IHasName, IHasAge, ecc. Ciò renderà il design molto più pulito e consentirà una migliore composizione di questo tipo di hiearchie.

    
risposta data 14.02.2013 - 23:37
fonte
3

Non è questo per le interfacce? Classi non correlate con funzioni diverse ma con una proprietà simile.

IName = Interface(IInterface)
  function Getname : String;
  property Name : String read Getname
end;

Class Dog(InterfacedObject, IName)
private
  FName : String;
  Function GetName : String;
Public
  Name : Read Getname;
end;

Class Movie(InterfacedObject, IName)
private
  FCount;
  FName : String;
  Function GetName : String;
Public
  Name : String Read Getname;
  Count : read FCount; 
end;
    
risposta data 14.02.2013 - 22:46
fonte
1

La tua domanda sembra essere inquadrata nel contesto di un linguaggio che non supporta l'ereditarietà multipla ma non specifica specificamente questo. Se stai lavorando con un linguaggio che supporta l'ereditarietà multipla, questo diventa banalmente facile da risolvere con un solo livello di ereditarietà.

Ecco un esempio di come questo può essere risolto utilizzando l'ereditarietà multipla.

trait Nameable { String name; }
trait Describable { String description; }
trait Countable { Integer count; }
trait HasImageUrl { String imageUrl; }
trait Living { String age; }

class A : Nameable, Describable;
class B : Nameable, Describable, Countable;
class C : Nameable, Describable, Countable, HasImageUrl;
class D : Nameable, Countable;
class E : Nameable, Countable, HasImageUrl, Living;
    
risposta data 25.02.2013 - 17:09
fonte

Leggi altre domande sui tag