Refactoring di una classe che ha molti membri

1

Devo apportare modifiche in una classe che ha molti membri, che sono impostati da una funzione che implementa alcuni algoritmi, e quindi vengono letti da qualche altra funzione che implementa qualche altro algoritmo.

class Clazz{

    private int[] someArray1;
    private string[] someOtherArray2;
    .
    .
    .
    .
    private DataStructure[] someArray10;

    public Output createOutput(Input input){
        algorithm1(input);
        return createOutput();
    }

    private void algorithm1(Input input){
        //many conditions and loops which change the data structures someArray1 ... someArray10
    }

    private Output algorithm2(){
        //many conditions and loops which read (or change?) the data from the data structures and finally produce another data structure.
    }
}

Voglio rifattorizzare questa implementazione in modo che tutte le funzioni possibili diventino "funzionali", in modo che non abbiano effetti collaterali e restituiscano un risultato ben definito nel valore restituito.

La soluzione immediata che ho trovato è quella di creare molti oggetti di% co_de nidificati che contengono i campi di ciascuna funzione e passare questi contesti da una funzione all'altra.

class Context10 {
    private DataStructure[] someArray10;
    /*ctor, get, set*/
}

class Context2 {
    private string[] someOtherArray2;

    private Context10 context10;
    /*ctor, get, set*/
}

.
.
.

class Context {
    private int[] someArray1;
    private Context2 context2;

    /*ctor, get, set*/
}

class Clazz{

    public Output createOutput(Input input){
        Context context = algorithm1(input);
        return createOutput(context);
    }

    private Context algorithm1(Input input){

        Context10 context10 = foo(input);
        Context2 context2 = bar(context10, input);
        Context2 context2_1 = baz(context2, context10, input);
        .
        .
        .

        Context returnContext = createReturnContext(context2, context10);
        return returnContext;
    }

    private Output algorithm2(Context context){
        /* use the data structures in the context to produce the output. */
    }
}

Questa soluzione sembra essere ingombrante e non troppo efficiente.

C'è qualche tecnica per affrontare questo tipo di classi?

    
posta alex440 15.07.2018 - 15:15
fonte

2 risposte

1

Da quello che posso dire con la tua soluzione semplice, hai raggruppamenti di questi membri che si applicano a determinati contesti, ma l'algoritmo si basa su un passaggio da un contesto all'altro.

class Context10 {
    private DataStructure[] someArray10;
    /*ctor, get, set*/
}

class Context2 {
    private string[] someOtherArray2;

    private Context10 context10;
    /*ctor, get, set*/
}

.
.
.

class Context {
    private int[] someArray1;
    private Context2 context2;

    /*ctor, get, set*/
}

class Clazz{

    public Output createOutput(Input input){
        Context context = algorithm1(input);
        return createOutput(context);
    }

    private Context algorithm1(Input input){

        Context10 context10 = foo(input);
        Context2 context2 = bar(context10, input);
        Context2 context2_1 = baz(context2, context10, input);
        .
        .
        .

        Context returnContext = createReturnContext(context2, context10);
        return returnContext;
    }

    private Output algorithm2(Context context){
        /* use the data structures in the context to produce the output. */
    }
}

Con solo queste informazioni, hai una collezione di operandi (possibilmente Elenco, dipende dal raggruppamento di membri), e l'operazione è unica per un dato contesto / passo. Inoltre, un operando può essere un altro contesto come visto nell'algoritmo1.

class Context
{
    private List<T> Operands;

    public Context(List<T> Operands) \set the group to whatever makes sense in your code
    {
        this.Operands = Operands;
    }
}

Prendendo il contesto di classe, puoi semplificare usando qualche interfaccia o classe base con List (qualunque collezione abbia senso) che manterrà tutti gli operandi. Infine, puoi tenere ogni contesto in un oggetto che fornisce qualcosa di più piccolo con cui lavorare durante il refactoring ulteriormente.

interface IContext
{
    List<T> Operands { get; set; };
}

class Foo
{
    List<IContext> Context;

    public Foo()
    {
        Context = new List<IContext>();
    }

    public void AddContext(IContext Context)
    {
        Context.Add(Context); \ change to a map/dictionary format if order is determined in another way
    }

    // here would be where things vary depending on what is needed
    // you now have a way to iterate over each context available as well as their operands within each context
}

Quindi, questo porterebbe a definire come ogni contesto interagisce l'uno con l'altro. Ad esempio, se ciascun contesto si costruisce uno sull'altro, puoi esaminare iterando ricorsivamente sulla raccolta e passando i risultati per riferimento.

Se c'è ancora qualche separazione da fare, come Context 3 & 4 sono raggruppati insieme ma non si basano sui risultati in nessuno degli altri contesti, quindi si può considerare suddividendolo in una propria collezione.

public class Foo
{
    List<IContext> GroupA;
    List<IContext> GroupOf3And4;
}

Per quanto riguarda il raggiungimento di un'altra soluzione, hai un minor numero di elementi / effetti collaterali in termini di una certa raccolta di elementi che ottieni un output basato sulla collezione. Per me, questo sarebbe un buon inizio, ma in realtà dipende dall'utilizzo effettivo. Come detto sopra, sembra che l'algoritmo / output si basi principalmente su un passaggio da un contesto all'altro. Con questo in mente, un approccio di refactoring delle interazioni può aiutare con la sensazione ingombrante quando si tenta di refactoring funzionalità.

Fammi sapere se ho frainteso ciò che stai cercando con qualcosa sopra.

    
risposta data 16.07.2018 - 16:52
fonte
3

Sospetto strongmente che il tuo context sia un oggetto God o almeno una struttura dati di Dio. Se ciò che stai passando contiene cose di cui le funzioni non servono, è molto probabile che lo sia davvero.

La tecnica per risolvere questo problema è iniziare a sfoltire ciò che non è necessario. Questo significa che piuttosto che una struttura dati per dominarli tutti, ne creerai molti diversi. Questo è buono, perché rende chiare le dipendenze. Dai alle cose solo ciò di cui hanno bisogno.

    
risposta data 15.07.2018 - 22:30
fonte

Leggi altre domande sui tag