Polimorfismo in classi quasi identiche

3

Al momento sto lavorando a un progetto con diverse istanze di abstract class . Alcune funzioni nelle sottoclassi sono le stesse, altre no. Questo porterebbe al codice come il seguente. Nel codice uso l'oggetto come parametro, ma nel progetto reale è una classe definita e non oggetto .

public abstract class BaseClass
{
    protected BaseClass Next;
    public abstract void foo(object SomeParameters);
    protected virtual void bar1()
    {
        object SomeParameters = this.GetMyParameters();
        foo();
        bar2();
    }

    public virtual void bar2()
    {
        if (this.Next!= null)
        {
            this.Next.bar2();
        }
    }
}

//==============================
//==============================

public class Child1 : BaseClass
{
    override public void foo(object SomeParameters)
    {
         ...
    }
}

public class Child2 : BaseClass
{
    override public void foo(object SomeParameters)
    {
         //some code
    }

    override public void bar2()
    {
         this.DoSomething();
         base.bar2();
    }

    private void DoSomething()
    {
        //some other code
    }
}

public class Child3 : BaseClass
{
    override public void foo(object SomeParameters)
    {
         ...
    }

    override public void bar1()
    {
         object SomeParameters = this.GetMyParameters();
         foo();
         Between();
         bar2();
    }

    private void Between();
    {
        //Code between foo and bar2
    }
}

I miei dubbi sono la leggibilità del codice, perché stiamo parlando di qualcosa come 15 sottoclassi. L'altro modo di aggirare sarebbe codificare le cose due o più volte, una violazione del principio ASCIUTTO. C'è qualche soluzione / Pattern che mi è mancato per liberarmi di questo paradosso?

Saluti Noran

    
posta Noran 21.10.2016 - 12:59
fonte

5 risposte

1

Modello di progettazione del metodo di modello

Il tuo design definisce nella classe base, un modello metodo , bar1() essendo il metodo template e foo() e bar2() sono i primitivi chiamati da quel metodo.

In Child2 sembra estendere la logica del metodo template bar1() inserendo un extra primitivo Between() nella logica. Sfortunatamente, questa primitiva è specifica per la classe, quindi è necessario sovrascrivere bar1() per quello scopo in quella classe.

Per migliorare notevolmente la leggibilità del tuo codice e semplificare la manutenzione, ti suggerisco di rifattorizzare quel codice in modo da avere solo una versione comune (completa) del metodo template . Quindi è necessario solo capire la logica generale del metodo template e quindi capire cosa è specifico in ogni classe.

 public abstract class BaseClass
 {
    ...
    protected virtual Between() {}  // by default, nothing
    public void bar1()              // one single skeleton
    {
         foo();
         Between();
         bar2();
    }
    ...
}
... 
public class Child3 : BaseClass
{
    override protected void Between();
    {
        //Code between foo and bar2
    }
}

A proposito, osservando da vicino, bar2() è anche un tipo di metodo template

public abstract class BaseClass
{
    ...
    protected virtual void DoSomethingBefore() {} 
    protected virtual void DoSomethingAfter() {} 
    public virtual void bar2()
    {
        DoSomethingBefore(); 
        if (this.Next!= null)
        {
            this.Next.bar2();
        }
        DoSomethingAfter(); 
    }
}
...
public class Child2 : BaseClass
{
    ...
    override protected void DoSomethingBefore()
    {
        ...
    }
}

Alcune funzioni private sono promosse a quelle protette. Ma le sue funzioni di utilità non sono ancora accessibili per il mondo esterno. Lo svantaggio è che hai un leggero sovraccarico, chiamando a volte primitive vuote.

Questo schema ha senso solo se ci sono lievi variazioni nei metodi, ma il nucleo è lo stesso per tutti.

Modello di progettazione della strategia

Un altro modo per gestirlo è utilizzare il strategia modello di progettazione . L'idea è di identificare comportamenti / algoritmi generici e di tenerli fuori dalla tua gerarchia di classi, per spostarli in classi strategiche intercambiabili.

Potresti implementare strategie statiche usando le interfacce. O potresti implementare strategie dinamiche usando la composizione.

Il vantaggio è che è molto più flessibile del metodo template. Tuttavia è molto diverso nella struttura: i metodi oggetto della strategia devono avere accesso alle classi Child . Pertanto sono limitati all'accesso pubblico, il che potrebbe costringerti a rendere pubbliche alcune funzioni di supporto invece di mantenerle private.

    
risposta data 22.10.2016 - 01:37
fonte
2

Stai pensando al modello di comportamento della strategia

Sembra che tu sia interessato all'utilizzo di diverse classi con funzionalità leggermente diverse. Nel tuo commento hai menzionato Kilian Foth che questi sono hardware con diversi lavori. So che ogni Hardware ha una serie di lavori. So anche (dato che non hai detto, ma di solito questo è quello che è un lavoro e sto facendo un'ipotesi) che i lavori hanno diversi passaggi che devono essere completati in ordine.

public abstract class Hardware 
{
    private List<Job> CurrentJobs;
    //Some set of other things that I don't care about.

    public void AddJob(Job j){
        CurrentJobs.Add(j);
    }

    public virtual void ExecuteJobs(){
        foreach (Job j in CurrentJobs)
        {
            j.Execute(argument);
        }
    }

    //Other non-virtual methods to manipulate CurrentJobs
    //Other methods as needed
}

public abstract class Job
{
    public virtual void Execute(Argument argument)
    {
        //Default behavior
    }
}

public class FancyJob : Job
{
    public virtual void Execute(Argument argument)
    {
        //Specialized Behavior
    }
}

Questo è un esempio molto semplice per ottenere il punto. Fondamentalmente, se hai un tipo di lavoro che funziona solo sulla stessa cosa (forse?) Puoi dire una lista, caricare quell'elenco in ordine, e poi eseguire i lavori senza sapere cosa siano. In pratica, vuoi rendere l'hardware abbastanza stupido da far conoscere solo i nomi dei lavori che deve eseguire. . Anche in questo caso se hai un particolare lavoro / passaggio simile, puoi riutilizzare il lavoro in più posti.

Se vuoi ottenere molto a grana fine, ogni Job potrebbe avere anche un elenco di passaggi da eseguire attraverso lo stesso meccanismo.

    
risposta data 21.10.2016 - 19:12
fonte
1

Utilizza un approccio funzionale.

public class BaseClass {
    public BaseClass(Action foo, Action bar1 = null, Action bar2 = null) {
        this.foo = foo;
        this.bar1 = bar1 ?? this.defaultBar1;
        this.bar2 = bar2 ?? this.defaultBar2;
    }

    private void defaultBar1() 
    {
    }

    private void defaultBar2() 
    {
    }

    private Action foo;
    private Action bar1;
    private Action bar2;
}

Puoi creare quanti più impli desideri con relativa facilità. Questa interfaccia può essere ottimizzata per consentire l'accesso all'istanza BaseClass, consentendo le tue variabili, ecc.

    
risposta data 21.10.2016 - 13:09
fonte
0

Il punto di ereditarietà è precisamente questo: permettere alle sottoclassi di sovrascrivere alcuni metodi mentre riusando tutti gli altri. Se il tuo codice è strutturato in questo modo e diventa illeggibile, non è a causa dell'override, ma perché ci sono così tante sottoclassi .

Hai bisogno di 15 sottoclassi? Cosa rappresentano? Puoi refactoring in modo che ci siano meno classi e più metodi, o più parametri? I decoratori potrebbero risolvere il tuo problema, forse?

    
risposta data 21.10.2016 - 13:08
fonte
-1

Potresti farlo usando più classi incoerenti o astratte.

Nel tuo esempio penso che stai dicendo che child1 e child2 potrebbero avere la stessa implementazione di foo (), tuttavia child2 ha una diversa bar2 (). Si potrebbe quindi rendere child2 ereditato da child1 e quindi non si dovrebbe ripetere il codice per foo (). Puoi farlo per ogni diversa implementazione di foo ()

Puoi anche avere più classi astratte seguendo questo modello

public abstract class BaseClass
{
    public abstract void foo();
    public abstract void bar();
}

public abstract class BaseClassFoo1 : BaseClass
{
    override public void foo()
    {
        doSomething();
    }
    public abstract void bar();
}

public abstract class BaseClassFoo2 : BaseClass
{
    override public void foo()
    {
        doSomethingElse();
    }
    public abstract void bar();
}

public abstract class BaseClassBar1 : BaseClass
{
    public abstract void foo();
    override public void bar()
    {
        doSomething();
    }
}

public abstract class BaseClassBar2 : BaseClass
{
    public abstract void foo();
    override public void bar()
    {
        doSomethingElse();
    }
}

public class Child1 : BaseClassFoo1
{
    override public void bar()
    {
         doSomething();
    }
}

Ogni volta che dovresti ripetere il codice, crea una classe base per quelle classi. Potrebbe non valerne la pena se tu avessi solo due classi con lo stesso codice ma più di quello e non vuoi il codice ripetuto.

    
risposta data 21.10.2016 - 18:50
fonte

Leggi altre domande sui tag