3

I have the constructor:

public class Player {
    final private DoubleDuple position;
    final private DoubleDuple momentum;

    public Player(double xPos, double yPos, double xMom, double yMom) {
        position = new DoubleDuple(xPos, yPos);
        momentum = new DoubleDuple(xMom, yMom);
    }
}

I have to allow the momentum to be passed in since I'm making the class immutable, so I'll need the ability to construct a Player with a different position, but with the previous momentum.

The problem is, this constructor now has 4 fields, and could get larger if later there are more fields that I need to pass into a newly constructed object (health, for example).

I could combine arguments by forcing the user to wrap positional coordinates and momentum values in a DoubleDuple before passing them in to the constructor, which will cut down on the number of arguments. I think this will also make it clearer that the first 2 and second 2 arguments are associated with each other. Unfortunately, this also has the effect of making each object construction bulkier, since there will be a few extra new DoubleDuples mixed in.

Is it better practice to have similar arguments that are wrapped internally passed in wrapped to the constructor, or should they be passed in "loose"?

  • 1
    Is DoubleDuple immutable? If so, allow passing one in for direct use. But don't force it for no good reason. – Deduplicator Aug 9 '15 at 1:23
  • Yes, it is immutable. The problem with that is, then I have to duplicate every method to allow either 2 doubles, or 1 DoubleDuple. – Carcigenicate Aug 9 '15 at 1:25
  • There is no need to use two doubles. Firstly it doesn't save typing (or only by a little bit). Secondly short-lived objects in Java are handled efficiently by GC. Just let the caller instantiate the DoubleDuple arguments. – rwong Aug 9 '15 at 6:38
  • In case you have seen articles which recommend passing in primitives, let me guess ... (1) article was written in 2004 and then rehashed by numerous famous people (bloggers) (2) working in internet stock exchange and write code for "no-GC Java", where objects don't have states, all states are stored in a section of a giant global integer array. (no, not kidding.) – rwong Aug 9 '15 at 6:40
  • (linked question) programmers.stackexchange.com/questions/293199/… – rwong Aug 9 '15 at 7:24
4

You can consider allowing your callers to pass in DoubleDuple. (To save some typing, below I will use Vec2 in place of DoubleDuple.)

Example:

public class Vec2 {
    public final double x;
    public final double y;
    public Vec2(double x, double y) {
        this.x = x;
        this.y = y;
    }
    public Vec2 add(Vec2 other) {
        return new Vec2(this.x + other.x, this.y + other.y);
    }
    // ... there should be other methods applicable to 2D vectors.
}

While it makes sense to make small objects immutable (to allow worry-free shared read-only access to getters without having to defensively clone it), I am not sure about making large objects immutable, because of the need to clone every one of its fields when a change needs to be made.

In other words, if the object is large (having many fields), and if its typical usage (semantically) often requires modification, and if there is no need to keep the old object state around (as in memento pattern), then I would think that modifying the object in-place would be a more natural way of programming in Java.

Note that this opinion isn't applicable to other languages, because languages that are designed for functional programming would have other mechanisms for resolving this issue.

Therefore, I would implement Player as follows:

public class Player
{
    // ... same fields and constructors as yours
    // ... but they will be mutable.

    public void setPosition(Vec2 newPos) {
        // Vec2 being immutable means you do not have to worry 
        // about the caller mutating newPos after calling 
        // Player.setPosition(Vec2)
        // Meanwhile, Player is mutable.
        this.pos = newPos; 
    }
    public void addPosition(Vec2 deltaPos) {
        // The cost we pay for making Vec2 immutable 
        // (in terms of lines of code) is minimal. However,
        // same cannot be said of the cost of making Player
        // immutable. My suspicion is that it could greatly
        // increase the lines of code for Player.
        this.pos = this.pos.add(deltaPos);
    }
    public Vec2 getPosition() {
        // Vec2 being immutable means you do not have to worry 
        // about the caller mutating the returned Vec2, which
        // would have caused havoc on the Player.pos field
        // if Vec2 isn't immutable.
        return this.pos;
    }
    // ... and others
}

Finally, you can consider putting pos and mom together, into a class Motion, and then add it as a field to the Player class.

// Motion can be mutable or immutable; 
// shown below is the mutable implementation.
public class Motion
{
    private Vec2 pos;
    private Vec2 mom;
    public Motion(Vec2 pos, Vec2 mom) { ... }
    public void setPosition(Vec2 newPos) { ... }
    public void addPosition(Vec2 deltaPos) { ... }
    public Vec2 getPosition() { ... }
    public void setMomentum(Vec2 newMom) { ... }
    public void addMomentum(Vec2 deltaMom) { ... }
    public Vec2 getMomentum() { ... }
    // ... add other methods as needed.
}

public class Player {
    private Motion motion;
    // ... constructors and other fields

    // ... Now, we face a dilemma. Motion has a lot of methods,
    // ... but we don't want to copy all those methods here.

    // Option 1 - bite the bullet. Each method will delegate
    // to the method of same name on the Motion instance.

    public void setPosition(Vec2 newPos) { ... }
    public void addPosition(Vec2 deltaPos) { ... }
    public Vec2 getPosition() { ... }
    public void setMomentum(Vec2 newMom) { ... }
    public void addMomentum(Vec2 deltaMom) { ... }
    public Vec2 getMomentum() { ... }

    // Option 2 - getters and setters
    // As discussed above, getters and setters benefit from
    // immutable objects by not having to defensively clone them.
    public Motion getMotion() { return motion; }
    public void setMotion(Motion newMotion) { motion = newMotion; }

    // Option 3 
    // Requires Java 8 Lambda. 
    // Requires the MotionUpdater interface below.
    // Motion can be mutable or immutable; doesn't care.
    public void changeMotion(MotionUpdater motionUpdater) {
        this.motion = motionUpdater.update(this.motion);
    }
}

// Used by Option 3 above. It can be implemented by a class
// (class, inner class, inline class, etc.), or Java 8 Lambda.
public interface MotionUpdater
{
    Motion update(Motion oldMotion);
}

By making this refactoring (extract class), now Motion class only has two fields, and therefore it qualifies for the immutability treatment, i.e. cloning a Motion to modify its values only involves copying two references to immutable Vec2.

To summarize, immutable objects are suitable when:

  • When it's needed by the application logic (need permanent records of objects; need frozen copies of objects shared by multiple threads, etc), or ...
  • When objects are very small (2-4 fields) and not part of any inheritance chain. Note that interface implementations is still allowed.
  • Thank you, thorough answer. What I wouldn't do for Scala's case-class's copy method. And I like the idea of making the "motion" aspects of Player a separate class, but for "option 2", is it really appropriate to have the caller pass in a Motion? On one hand, it's a similar situation as the caller passing in a DoubleDuple, but on the other, while DoubleDuple is a simple container class, Motion would seem to be a more involved implementation detail that should be hidden. Regardless, you've given me something to think about. Thank you. – Carcigenicate Aug 9 '15 at 14:57
  • @Carcigenicate this is why I mark them as "options". Choose something you like and feel comfortable. It is not necessary to implement all of them anyway. (Of course, you can also choose something you don't like and feel uncomfortable, in the so-called "think outside the box" style.) – rwong Aug 9 '15 at 20:57

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.