11

I have a situation where I want to call a function which requires a number of parameters. This function is not called directly, it is called indirectly and the parameters are delegated several times. Some extra parameters may be added in between the different layers, but those aren't included in the example for the sake of simplicity.

In pseudo code, A indirectly calls the functionality that occurs in C.abc.

class A {
    private B b;

    public int abc() {
        return b.abc(value1, value2, value3);
    }
}

class B {
    private C c;

    public int abc(int x, int y, int z) {
        return c.abc(x, y, z);
    }
}

class C {
    private int w = 100;

    public int abc(int x, int y, int z) {
        // Do something with parameters x, y, z, e.g.:
        return w + x + y + z;
    }
}

The number of parameters could be different than three of course: it could be smaller or larger as long as the parameters are going to be used together. The Introduce Parameter Object is sometimes advised in such a situation (here and here).

It would be applied as follows:

class XYZParameterObject {

    private int x, y, z;

    public constructor(int x, int y, int z) {
        this.x = x;
        this.y = y;
        this.z = z;
    }

    public int abc(int w) {
        return w + x + y + z;
    }
}

class A {
    private B b;

    public int abc() {
        return b.abc(new XYZParameterObject(value1, value2, value3));
    }
}

class B {
    private C c;

    public int abc(XYZParameterObject xyz) {
        return c.abc(xyz);
    }
}

class C {
    private int w = 10;

    public int abc(XYZParameterObject xyz) {
        // Do something with parameters x, y, z, e.g.:
        return xyz.abc(this.w);
    }
}

To me this seems like a class is designed around just one method, while it does not have any behavior. It would actually be the same as passing a closure instead of creating a XYZParameterObject instance. In both situations the x, y, z parameters are reduced to simply one parameter. Both looks like abusing a class just for the sake of not having to pass the same arguments over and over. On the other hand, that would be somewhat convenient of course!

So what is an advisable practice in such a situation?

Nick Alexeev
  • 2,532
user2180613
  • 1,792

4 Answers4

8

A parameter object is just a way of collecting a set of parameters into a unit. But in your example you are doing something more - your are adding business logic (the addition) into the parameter object itself (which in turn means the C class becomes is superfluous). But then it is not just a parameter object anymore.

A parameter object is not a class built around a single method, it is a data transfer object, which means it does not have methods at all (or at at least only methods for accessing data). But it could be the starting point for a real class with business methods.

Whether a parameter object is a good idea in the first place depends on your code. Your example is too abstract to tell. But if x, y and z say represented coordinates for a point in 3D it would probably be useful to group them in a parameter object, but probably also useful furthermore to add some business logic for calculations on points, e.g scaling, adding etc.

JacquesB
  • 61,955
  • 21
  • 135
  • 189
2

Is “Introduce Parameter Object” actually a good pattern?

Yes. In every respect.

It makes code more readable and in consequence more comprehensible: the less information you have to parse, the easier to understand.

From this point of view, it makes sense to refactor a list of parameters to an object. And if you give this object a meaningful name, you win double.

After your edit:

To me this seems like a class is designed around just one method, while it does not have any behavior.

Putting one method into the object doesn't make (in the context you provided) sense.

A call to a.abc() delegates the call to b.abc(), which itself does something with the values contained in your value object. So the place, where the method should belong is from your example C.

The method only makes sense, when a call to it should be reusable - say A needs the result to inform a possible D about it.

So what is an advisable practice in such a situation?

It is okay, to use a value object even for the purpose of simply shortening parameters list. It makes the code more readable.

Add methods only, when there are benefits in more than one place from that.

Thomas Junk
  • 9,623
  • 2
  • 26
  • 46
1

A better question than "is <refactoring> good" is: what does this refactor do for your code?

Refactorings are tools

Refactorings by themselves are neither good nor bad; they are simply a way to reexpress code in a way that behaves identically. After all, many refactoring are paired: extract method and inline method move in opposite directions from each other. Whether they are situationally good or bad depends on what benefits they bring:

  • Do they immediately simplify the code? This is often viewed by newbies (or even experienced engineers, sadly) as the only value of a refactoring.
  • Do they allow further refactoring? Chained refactoring may initially complicate code, but end up producing a more cohesive whole at the end. For instance, if you find a bunch of functions with bad names, lots of inputs and outputs, and chained in sequence, inlining them all may be the first step in extracting out better methods.
  • Do they simplify a code change? Sometimes, something that makes the code more complex, can pay for itself when it simplifies a big change you need to make. Before adding a new type of data a system needs to handle, for instance, you might first move around your existing code to end up with an interface that gives you a clear extension point for the new behaviour.

This last one is a real power tool, and sadly missed by a lot of beginners (and also, oddly, by TDD, which insists on refactoring only after making a change).

Above all, no refactoring is "always good". You should never blindly apply any refactoring, just because it's there. (This is true for any pattern!) This will likely result in obfuscated code where the true intent has been lost.

Should you use this tool

So: does this change make your code simpler? Or make a new piece of code you need to add simpler? Maybe Introduce Parameter Object is a first step in a multi-part refactor, where you end up moving a bunch of your business logic onto this new class, encapsulating it better and making your whole code more readable. Or maybe you need to add more data to the system, and instead of adding more parameters to every method and passing them all through, you first introduce a parameter object, so adding those new data items is trivial.

(Another pro-tip: sometimes a refactoring only becomes obvious when you try to make a change. Don't be afraid to give up your initial attempt, and start clean on a fresh branch with a nice refactor. You can always recover salient bits of your initial attempt from your SCM.)

Code is an art; try things out, and see how they work. Readability is subjective; chat with your teammates (if you are working in a team) to see what they think. Or raise a PR to solicit informed feedback, if that's easier.

Ultimately, only you can answer whether it is good.

0

You need to pass information from caller to callee. The amount of information can usually not be changed. You can divide this information into lots of primitive parameters, or you can combine some or all primitives into parameter objects.

Parameter objects are useful in some situations. One is if the callee calls another function with the exact some primitives, so it can just pass the parameter object. If the number of meaning of the primitives changes as your code is developed, then the parameter object can be changed, while the function declaration and call stay unchanged. Even better if the parameter object is passed straight to another function, then the function in the middle stays unchanged.

If there is a structure to your primitives and you can combine that structure in the parameter object, that makes your code easier to understand. Say you have a customer represented by five primitive objects and an item to buy represented by three primitives. You change it to two parameter objects. In this case they might not be just parameter objects but actual generally useful objects.

It’s usually not useful if there is no relationship between the parameters. So if you have a customer object and a purchased item object, I wouldn’t put both into a parameter object but pass two parameters.

gnasher729
  • 49,096