1

I have to make changes in a class which has many members, which are set by a function which implements some algorithm, and then are read by some other function which implements some other algorithm.

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.
    }
}

I want to refactor this implementation so that as many functions as possible become "functional" - so that they don't have side effects and return a well defined result in the return value.

The straighforward solution I came up with is to create many nested Context objects which contain the fields each function changes, and pass these contexts from one function to another.

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. */
    }
}

This solution seems to be cumbersome and not too efficient.

Is there some technique to deal with this kind of classes?

  • 1
    Are you trying to eliminate all state in favor of testability? – fstam Jul 15 '18 at 16:13
  • 1
    As far as the members, are they all collections, and are these members mainly primitive data types or a combination of any types? Mainly asking because I wonder if refactoring just the members would help with finding other solutions. – eparham7861 Jul 15 '18 at 17:41
  • @wasted Yes, I am trying to eliminate all shared state in favor of explicit communication between the units of work. The class in question does not posess any "actual" information or state relevant to the business. All of it's data structures are a sort of "global temps" used for communication between several functions. – alex440 Jul 16 '18 at 7:13
  • @eparham7861 There were a couple of collections of data structures, several collections of primitives, and also a bunch of primitives - flags, counters etc. How can it help us to find other solutions? – alex440 Jul 16 '18 at 7:18
1

From what I can tell with your straightforward solution, you have groupings of these members that apply to certain contexts, but the algorithm relies on stepping through each context.

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. */
    }
}

With just that information, you have a collection of operands (possibly List, depends on the grouping of members), and the operation is unique to a given context/step. Also, an operand can be another context as seen in the Algorithm1.

class Context
{
    private List<T> Operands;

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

Taking the class Context, you can simplify by using some interface or base class with List (whatever collection makes sense) which will hold all operands. Lastly, you can hold each context in one object which provides something smaller to work with when refactoring further.

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
}

So, this would lead to defining how each context interacts with each other. For example, if each context builds upon each other, then you can look into recursively iterating over the collection and passing the results by reference.

If there is still some separation that needs to be done, such as Context 3 & 4 are grouped together but do not rely on the results on any of the other contexts, then you may look at breaking that apart into its own collection.

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

As far as getting to another solution, you have fewer items/side effects in terms of given some collection of items you get an output based on the collection. To me, this would be a good start, but it really depends on the actual usage. As stated above, it does appear that the algorithm/output mainly relies on stepping through each context. With that in mind, an approach of refactoring the interactions can help with the cumbersome feeling when trying to refactor functionality.

Let me know if I have misunderstood what you are after with anything above.

  • Yes, this approach can pave the way to the ultimate goal of extracting classes from the procedures. I may create an abstraction of the context to manage these connections in a consistent way. – alex440 Jul 18 '18 at 7:55
3

I strongly suspect your context is a God object or at least a God data structure. If what you're passing around contains things the functions don't need, it's very likely indeed.

The technique to fix this is to start pruning off what isn't needed. This means rather than one data structure to rule them all, you'll create many different ones. This is good, because it makes the dependencies clear. Give things only what they need.

  • Well, the higher level functions are sort of "god functions", that make use of all the data structures. That's why I try to build the god context from smaller contexts and pass lower level functions the contexts which contain only the things they need. That's exactly the structure I am trying to find a way to make more elegant. – alex440 Jul 16 '18 at 7:03

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.