Duplicazione in gerarchie di ereditarietà parallele

7

Usando un linguaggio OO con tipizzazione statica (come Java), quali sono i modi migliori per rappresentare il seguente modello invariato senza grandi quantità di duplicazione.

Ho due (in realtà più) sapori della stessa struttura. Ogni aroma richiede il proprio (unico per i dati di sapore) su ciascuno degli oggetti all'interno di quella struttura, nonché alcuni dati condivisi. Ma all'interno di ciascuna istanza dell'aggregazione sono consentiti solo gli oggetti di uno (lo stesso) sapore.

FooContainer può contenere FooSources e FooDestinations e associazioni tra gli oggetti "Foo" BarContainer può contenere BarSources e BarDestinations e associazioni tra gli oggetti "Bar"

interface Container()
{
   List<? extends Source> sources();
   List<? extends Destination> destinations();
   List<? extends Associations> associations();
}

interface FooContainer() extends Container
{
   List<? extends FooSource> sources();
   List<? extends FooDestination> destinations();
   List<? extends FooAssociations> associations();
}

interface BarContainer() extends Container
{
   List<? extends BarSource> sources();
   List<? extends BarDestination> destinations();
   List<? extends BarAssociations> associations();
}

interface Source
{
   String getSourceDetail1();
}

interface FooSource extends Source
{
   String getSourceDetail2();
}

interface BarSource extends Source
{
   String getSourceDetail3();
}

interface Destination
{
   String getDestinationDetail1();
}

interface FooDestination extends Destination
{
   String getDestinationDetail2();
}

interface BarDestination extends Destination
{
   String getDestinationDetail3();
}

interface Association
{
   Source getSource();
   Destination getDestination();
}

interface FooAssociation extends Association
{
   FooSource getSource();
   FooDestination getDestination();
   String getFooAssociationDetail();
}

interface BarAssociation extends Association
{
   BarSource getSource();
   BarDestination getDestination();
   String getBarAssociationDetail();
}
    
posta flamingpenguin 15.03.2011 - 17:39
fonte

3 risposte

2

La programmazione generica è tua amica:

interface Association<S extends Source, D extends Destination> {
   S getSource();
   D getDestination();
}
interface FooAssociation extends Association<FooSource, FooDestination> {
   String getFooAssociationDetail();
}
interface BarAssociation extends Association<BarSource, BarAssociationDetail> {
   String getBarAssociationDetail();
}

interface Container<S extends Source, D extends Destination, A extends Association<S, D>> {
   List<? extends S> sources();
   List<? extends D> destinations();
   List<? extends A> associations();
}
interface FooContainer extends Container<FooSource, FooDestination, FooAssociations> {}
interface BarContainer extends Container<BarSource, BarDestination, BarAssociations> {}    

Non sono un programmatore Java, quindi scusate eventuali errori. Dovrebbe darti comunque un'idea.
Si noti che i vincoli dei parametri di tipo non sono necessari per costruire la gerarchia di tipi, ma comunicano meglio lo scopo delle interfacce di base generiche.

    
risposta data 10.07.2011 - 23:16
fonte
0

Ho visto e applicato, un codice simile come questi, a volte chiamato "doppia gerarchia", ma è lo stesso, con l'unica differenza che usa le classi direttamente, non attraverso le interfacce. In realtà avevo la stessa domanda, perché sembrava complessa e pensavo che stavo codificando qualcosa nel modo sbagliato.

Di solito non codice in java, ma dovrebbe essere qualcosa del tipo:

containers.j

package containers;

/* put base classes in single file */

class JSource
{
   String getSourceDetail1();
}

class JDestination
{
   String getDestinationDetail1();
}

class JAssociation
{
   Source getSource();
   Destination getDestination();
}

public class JContainer()
{
   List<JSource> sources();
   List<JDestination> destinations();
   List<JAssociations> associations();
}

foos.j

/* put extended subclasses in single file, independant from base classes */

import containers;

package foos;

class JFooSource extends JSource
{
   String getSourceDetail2();
}

class JFooDestination extends JDestination
{
   String getDestinationDetail2();
}

class JFooAssociation extends JAssociation
{
   JFooSource getSource();
   JFooDestination getDestination();
   String getFooAssociationDetail();
}

public class JFooContainer() extends JContainer
{
   List<JFooSource> sources();
   List<JFooDestination> destinations();
   List<JFooAssociations> associations();
}

bars.j

/* put extended subclasses in single file, independant from base classes */


import containers;

package bars;

class JBarContainer() extends JContainer
{
   List<JBarSource> sources();
   List<JBarDestination> destinations();
   List<JBarAssociations> associations();
}

class JBarSource extends JSource
{
   String getSourceDetail3();
}

class JBarDestination extends JDestination
{
   String getDestinationDetail3();
}


class JBarAssociation extends JAssociation
{
   JBarSource getSource();
   JBarDestination getDestination();
   String getBarAssociationDetail();
}

La differenza principale è che le classi base si trovano in un file / pacchetto separato, che indica la relazione tra queste classi. Quindi, ogni caso in cui sono richieste sottoclassi delle classi base, utilizzo un altro file / pacchetto per le sottoclassi, "foos.j" e "bars.j".

Suppongo che non vi sia alcun motivo per mescolare "foos" e "barre" nello stesso codice. Con la differenza di utilizzare classi anziché interfacce e file separati, non c'è altro modo per semplicemente questo "Design Pattern".

Alcuni esempi delle "gerarchie di ereditarietà parallele" sono menzionate nel libro "Modelli di design". Non è un modello di design in sé. Personalmente, lo vedo come un nuovo modello separato, diverso dal "Composite Patern".

    
risposta data 15.03.2011 - 23:09
fonte
0

Un approccio che funziona con qualsiasi linguaggio OO che supporti la riflessione è usare il modello "Fabbrica". Il modo in cui funzionerebbe (usando il codice Java) sarebbe quello di stabilire una convenzione di denominazione per le tue classi. Ad esempio, se XXXSource avesse un codice di visualizzazione specializzato, avresti una XXXView corrispondente in un pacchetto appropriato. Il tuo TreeWalker avrebbe una logica semplice simile a questa:

class TreeWalker
{
    public void displayContainer(Container container)
    {
        for(Source source : container.sources())
        {
            DisplayFactory.display(source);
        }
    }
}

La classe DisplayFactory avrebbe un pacchetto predeterminato che sta cercando e funzionerebbe in modo simile a questo:

class DisplayFactory
{
    String displayPackage = "com.mycompany.source.views.";

    public void display(source)
    {
        String sourceName = source.getClass().getName();
        String viewName = displayPackage
            + sourceName.substring(0,
                sourceName.length() - "Source".length())
            + "View";

        IView view = null;

        try
        {
            Class viewClass = Class.forName(viewName);
            view = viewClass.newInstance();
        }
        catch(Exception e)
        {
            // no specialized view, using default
            view = new DefaultView();
        }

        view.display(source);
    }
}

Il risultato finale è che devi solo creare viste specializzate per le fonti che desideri e ricorrere all'implementazione predefinita in tutti gli altri casi. La convenzione di denominazione salva un sacco di magia di configurazione ed è in realtà un modo abbastanza comune per risolvere il problema.

Il rovescio della medaglia è che potresti avere penalizzazioni delle prestazioni a causa di più oggetti e usare la gestione delle eccezioni per determinare quando nessuna classe esiste. Supponendo che le viste non contengano lo stato, è possibile ottimizzare un bit memorizzando nella cache l'istanza View sul nome della classe di origine. Se la ricerca rapida fallisce, ricorri all'approccio più pesante e memorizza l'istanza della vista che hai trovato per la prossima volta.

Risposta originale

In base alla tua descrizione e al tuo commento, sembra che tu voglia fare un piccolo rendering basato sui dati. Hai considerato che il tuo codice di rendering funzionasse con la classe Container di base? Quello sarebbe l'intero scopo dietro una gerarchia di ereditarietà come questa.

Finché la tua classe base Container ha i metodi necessari per navigare correttamente all'interno degli oggetti, non è necessario conoscere nulla sulle implementazioni più specifiche. Se è necessario eseguire il rendering di alcuni dati in modo più specifico, è possibile trarre vantaggio dal fatto che gli elementi dei dati hanno tipi diversi.

class TreeWalker
{
    public void displayContainer(Container container)
    {
        for (Source source : container.sources())
        {
            displaySource(source);
        }
    }

    private void displaySource(FooSource foo)
    { /* display foo specific source info ... */ }

    private void displaySource(BarSource bar)
    { /* display bar specific source info ... */ }
}

Il metodo core displayContainer sa come lavorare con le classi / interfacce di base che stai usando per definire la tua collezione di oggetti più specifica. Puoi utilizzare una factory per creare i renderer più specifici, ecc. L'idea di base è che tu stia lavorando con la classe base / l'interfaccia per gestire gli algoritmi più generici.

    
risposta data 18.04.2011 - 22:08
fonte