Sta usando l'ereditarietà per impostare le proprietà male?

3

Un collega e io stiamo discutendo. Nella nostra app ci sono componenti che si comportano in modo leggermente diverso in base a come vogliamo che vengano presentati. Finora, abbiamo concordato che la nostra eredità dovrebbe assomigliare a:

     Component
     /      \
  Fixed    Resizeable

in cui Fixed avrebbe una proprietà size e Resizeable avrebbe proprietà per i limiti e la dimensione iniziale. Tuttavia, ci sono ~ 20 componenti diversi che si adattano a 4 varianti: large , medium , medium-large (ridimensionabile) e medium-small (ridimensionabile).

Non siamo d'accordo su come i componenti dovrebbero essere impostati in queste varianti.

La sua soluzione:

// make a class that sets the properties of the variant
class MediumLargeResizeableComponent extends ResizeableComponent {
    MediumLargeResizeableComponent() {
        super(MEDIUM, LARGE); // some constants for the bounds
    }
}

class CustomComponent extends MediumLargeResizeableComponent {
    CustomComponent() {
        // doesn't have to set the size properties
    }
}

Questa soluzione utilizza una classe diversa per ogni variante che imposta le proprietà. Lo vedo come un problema perché hai bisogno di una nuova classe per ogni variante e non mi sembra un buon uso dell'ereditarietà. Pensa che sia più facile capire cosa sta facendo la classe. Dice anche che rende più facile cambiare tutte le classi che usano quella variante.

La mia soluzione:

// inherit directly and set the properties 
class CustomComponent extends ResizeableComponent {
    CustomComponent() {
        super(MEDIUM, LARGE); // constants for the bounds
    }
}

Questa soluzione è più semplice e lascia la personalizzazione al componente.

L'unica ragione tecnica che posso vedere contro la sua soluzione è che la tecnica non si adatta ad altre proprietà senza creare molte varianti. Non vede questo come un problema perché non abbiamo bisogno di altre proprietà come questa.

La sua soluzione - usare l'ereditarietà per impostare le proprietà - un modello cattivo? O è accettabile in situazioni come questa?

Modifica:

Mi sento come potrebbe esserci una certa confusione. Le 20 diverse classi sono semplicemente componenti diversi, ognuna delle quali fa cose diverse che sono indipendenti, ma ognuna di queste classi misura solo 1 delle varianti di dimensione. Come sopra, CustomComponent è ridimensionabile da medio a grande e nient'altro.

    
posta kmdreko 29.11.2016 - 04:41
fonte

4 risposte

8

Sono arrivato ad amare il polimorfismo, ma l'ereditarietà mi fa impazzire. Le catene di ereditarietà mi danno gli alveari. Perché? Esse portano al yo yo problem . Oh, certo, pensi che la tua catena sia piccola e carina ora, ma crescono, masticano le ore dei mobili, e molto presto sono io quello che finisce per prendermi cura di loro.

Quindi sì, preferirei il tuo al di sopra del suo, ma, beh ...

Potrebbero esserci altri motivi per fare ciò che stai facendo ma ciò che mi hai mostrato finora mi fa venir voglia di creare:

large = new BoundedComponent(LARGE, LARGE);
medium = new BoundedComponent(MEDIUM, MEDIUM);
mediumLarge = new BoundedComponent(MEDIUM, LARGE);
mediumSmall = new BoundedComponent(SMALL, MEDIUM);

Perché? Bene per uno mi spinge a vedere un parametro diventare parte di un nome di classe. Ti prego, dimmi che i tuoi 20 componenti diversi non saranno variazioni su SmallLargeResizeable . Dio ci aiuti se dobbiamo aggiungere piccoli o extra large.

In breve, non mi hai affatto venduto l'idea di Fixed . Il polimorfismo è una cosa meravigliosa ma non penso che sia stato pensato per questo. Di solito raggiungo la composizione prima dell'eredità, ma non ne vedo la necessità. I limiti semplici sembrano risolvere questo problema.

-

Fixed would have a size property

Questo qui è sospetto. Solo Fixed ottiene una proprietà dimensione? Quindi ora devo SAPERE che sto parlando di Fixed ? Non posso trattarlo come un Component ?

and Resizeable would have properties for bounds and initial size

E ancora devo SAPERE con cosa sto parlando. I limiti e le dimensioni iniziali vivono solo su Resizeable , quindi è meglio non chiamarli su Fixed . Non voglio sapere i problemi dei componenti! Basta mostrare già un componente sciocco.

Per giustificare questi componenti di dimensioni, quello che voglio sono metodi che posso chiamare che fanno QUALSIASI che facciano senza sapere cosa faranno. Queste cose possono essere diverse a causa di ciò che sono realmente, ma quando le uso non voglio pensarci.

someComponent.display() dovrebbe essere qualcosa che posso fare per qualsiasi componente. Se quel componente era un new BoundedComponent(LARGE, LARGE) o un new TextBoxComponent(new Resizeable(MEDIUM, LARGE)) non sono affari miei.

Una volta che lo chiamo, è molto bello quando ciò che viene visualizzato cambia in base a ciò che si nascondeva effettivamente sotto le copertine. Se hai un comportamento diverso che vorresti esprimere con tutti i mezzi, crea alcune classi polimorfiche per esprimerlo. Ma dovrebbe essere più di una semplice enumerazione.

    
risposta data 29.11.2016 - 05:48
fonte
5

Eventualmente scoprirai che ci sono un certo numero di funzionalità che i componenti possono avere e ogni componente ha zero o più di queste caratteristiche. Le dimensioni ridimensionabili e fisse sono solo due delle molte funzionalità con cui probabilmente andrai a finire. Questo è un problema classico che devono affrontare tutti i progettisti di framework di componenti.

Se il tuo linguaggio di programmazione non supporta l'ereditarietà multipla, allora non esiste una struttura di ereditarietà che funzioni. Il problema è che ci sono troppe permutazioni di funzionalità e non si adattano naturalmente a una gerarchia di ereditarietà. In effetti andrei oltre dicendo che ci sono pochissimi problemi in cui l'ereditarietà è una buona scelta, e ho strongmente scoraggiato l'uso dell'eredità per molti anni senza alcun rimpianto.

La soluzione adatta al tuo problema sono i mixin. Non penso che tu abbia menzionato la lingua in cui scrivi, ma esistono schemi standard per l'implementazione di mixin per la maggior parte delle lingue.

In breve, ti sbaglia entrambi. Dovresti definire interfacce come IFixedSize IResizable IContainer IDrawable etc, quindi puoi creare componenti che implementano qualsiasi combinazione di queste interfacce.

Il problema con l'ereditarietà è che crea struttura, e con quella struttura viene la rigidità. Quello che vuoi veramente nel tuo software è la flessibilità, quindi l'ereditarietà è sempre pessima.

Il motivo per cui i programmatori usano l'ereditarietà è di riutilizzare la funzionalità nella classe base. Accettano la rigidità come il costo del riutilizzo del codice, ma trascurano il fatto che ci sono altri modi per riutilizzare il codice che non ha questo costo.

Considera il seguente esempio:

public interface ISize
{
    float Width { get; set; }
    float Height { get; set; }
}

public interface ISizeFactory
{
    ISize Create();
}

public class MyComponent : ISize
{
    private ISize _size;

    public MyComponent(ISizeFactory sizeFactory)
    {
        _size = sizeFactory.Create();
    }

    public float Width
    {
        get { return _size.Width; }
        set { _size.Width = value; }
    }

    public float Height
    {
        get { return _size.Height; }
        set { _size.Height = value; }
    }
}

In questo esempio la classe MyComponent "eredita" la possibilità di modificare le dimensioni da un'implementazione riutilizzabile senza sacrificare alcuna flessibilità. In questo esempio, qualsiasi componente può essere aggiunto ad essa senza alcun obbligo di ereditare da una specifica classe base che potrebbe venire con un intero carico di bagaglio indesiderato.

Offre flessibilità totale e riutilizzo completo del codice: tutti i vantaggi dell'ereditarietà e nessuno dei costi.

    
risposta data 29.11.2016 - 07:24
fonte
0

Ciò di cui hai bisogno è una varietà di istanze . Non hai bisogno della stessa varietà nelle classi, né le 20, né le 4 sottoclassi intermedie. Non dovremmo creare (nuove, sotto) classi quando le istanze di una classe (base) esistente faranno il lavoro.

Ogni nuova classe introdotta presenta un accoppiamento più stretto rispetto all'ideale a causa dell'invocazione del costruttore. Per allentare quell'accoppiamento, introdurremmo un meccanismo di iniezione di fabbrica o dipendenza come intermedio aggiuntivo, e quindi avremo tutte quelle macchine più che sono sostanzialmente una complessità inutile.

Puoi ottenere ciò di cui hai bisogno usando solo le classi di livello superiore descritte (3) più un meccanismo di fabbrica. (E potresti anche non aver bisogno di tutti e 3 di quelli ...)

    
risposta data 29.11.2016 - 07:32
fonte
0

Is using inheritance to set properties bad?

Sì, perché utilizzi l'ereditarietà. Come bikeman868 , anch'io ho rinunciato all'ereditarietà e non ho mai guardato indietro. L'ereditarietà porta così tanti problemi al tuo codice . Stai meglio senza usarlo.

Nella situazione che descrivi, ci sono un certo numero di soluzioni che puoi adottare senza ricorrere all'ereditarietà. Ad esempio, se sei sicuro che probabilmente avrai solo bisogno delle varianti di taglia per:

  • grande,
  • medio,
  • medio-grande (ridimensionabile) e
  • medio-piccola

Quindi potresti semplicemente creare quattro interfacce: ILarge , IMedium , IMediumLarge e IMediumSmall e avere quei 20 componenti che ne implementano uno.

Naturalmente, in realtà, la tua implementazione potrebbe non essere così semplice, a seconda di quanta funzionalità-bagaglio è coinvolto in queste varianti. Ma una qualche forma di composizione fornirà una soluzione migliore rispetto a uno dei tuoi modelli di ereditarietà.

    
risposta data 29.11.2016 - 10:13
fonte

Leggi altre domande sui tag