Best practice: instance variables filling over time
Asked Answered
C

7

6

I'm new to the concept of object oriented programming (in java) and I often encounter the following design problem:

I often create classes with instance variables which are not known at the moment of initialization. These variables are filled over time. This is not a real problem since these variables are null until they are filled, my question therefore is more about the best practice in such situations.

Let me explain it with an example. I have a class Car. Every Car has a color, vMax, weight, horsepower etc.

When initializing the Car only it's color, weight and horsepower are known. --> Car(color, weight, horsepower)

The vMax can now be calculated (let's say: weight/horsepower). What confuses me is, that the Car, after initialization is "incomplete", meaning that the vMax will only be filled over time.

I found this quite ugly, and of course the car-example is simplified. I often have classes with 10+ properties where some are calculated over time, which themselves later in the code are used to calculate even more properties of that object. It then becomes difficult to know which variables are already filled at a certain point and which are not.

I just wondered whether this is "normal" and the way OOP works, or whether such situations should be avoided. If yes, I'd be glad for some design-hints.

Michael

Carlist answered 20/4, 2015 at 10:16 Comment(3)
I'd suggest looking into the fluent builder pattern, using nullable properties (so default primitive values are not confused with unassigned values) and validating all necessary properties in one or more private methods, invoked when necessary.Zymolysis
Seems like vMax should be a method rather than a variable...Latonya
@Latonya - I would prefer a transient variable instead of a method for vMax :)Allyl
A
3

As you define vMax should be a method more than an attribute of the class:

class Car {
    private String color;
    private double weight;
    private double horsePower;

    // constructor for Car
    public Car (String color, double weight, double horsePower) {
        this.color = color;
        this.weight= weight;
        this.horsePower= horsePower;
    }

    // usually I would create this as one-line-method...  but to clarify:
    public double getVMax() {
        double vMax = weight / horsePower; // calculate vMax!!!
        return vMax;
    }
}
Arrangement answered 20/4, 2015 at 10:25 Comment(3)
I think this is the way to go, at least for the properties that can be calculated from the already existing properties. It is not necessary to have an actual instance variable for an object to have a property, a method that calculates it is also good.Breuer
I thought of the exact same solution. This is also in line with the Tell Don't Ask principle. That is, instead of the client code asking double vMax = car.getWeight()/car.getHorsePower, the client can just say car.getVMax()Nollie
For the oversimplified example of the question I think this makes sense, but if you replace the trivial calculation of vMax by something that requires a little bit more computational effort it would make sense to store the value on a instance variable, leading again to the uncomfortable situation OP described. I just think OP has to get a little bit more used to using "getters" for calculated properties, and using those getters to verify if the property has already been calculated or not.And
M
1

It all depends on what you need.

The very first thing that comes to my mind is to use builder (especially taken into account that you mentioned "classes with 10+ properties")

The main idea of the builder is you don't create an object, until you know/calculate all its properties.

Let's get back to your car example.

class Car
{
    final int weight;
    final int horsepower;
    final int vMax;
    final String color;

    private Car(int weight, int horsepower, int vMax, String color)
    {
        this.weight = assetNotNull(weight);
        this.horsepower = assetNotNull(horsepower);
        this.vMax = assetNotNull(vMax);
        this.color = assetNotNull(color);
    }

    //..... other car-related methods

    /** Car builder */
    public static class Builder
    {
        int weight;
        int horsepower;
        int vMax;
        String color;

        public Builder setWeight(int weight)
        {
            this.weight = weight;
            return this;
        }

        public Builder setHorsepower(int horsepower)
        {
            this.horsepower = horsepower;
            return this;
        }

        public Builder setvMax(int vMax)
        {
            this.vMax = vMax;
            return this;
        }

        public Builder setColor(String color)
        {
            this.color = color;
            return this;
        }

        public Car createCar()
        {
            return new Car(weight, horsepower, vMax, color)
        }
    }
}

Then you can build your car like this

Car.Builder builder = new Car.Builder();
Car car = builder.setColor("blue")
            .setHorsepower(10)
            .setvMax(100)
            .setWeight(1000)
            .createCar();

This approach has the next benefits:

  • Car object is built when all required fields are set. If you need additional validation, you can add it to the createCar() method
  • You can pass Builder objects between different methods to fill in the fields.
  • Whenever you have a Car object, you are confident that it's valid (it cannot be instantiated otherwise)
  • your Car object is thread safe :)

Hope that helps.

Monomerous answered 20/4, 2015 at 10:46 Comment(4)
Thank you! (also @Zymolysis for the comment in the original post) for pointing me to the builder pattern. I think that's what I'm going to to have a look at! The builder will be "incomplete" as well of course until all fields are filled, but since it's a builder that's exactly what it's meant for, so will make my code much ¨clearer, and there won't be inclomplete (final) objects around. Thank youCarlist
I don't think a builder helps you. Because you still have to specify all the properties you need before actually creating the car. From what you describe, some of the properties are only set some time after actually using the object.Breuer
Also, having a setVMax() method seems dangerous to me. Since the vMax is calculated in terms of weight and horsepower, you could crate an inconsistent object, in which vMax is different than weight/horsepower.Breuer
@AndreiVajnaII In a simplified Car-situation you're absolutely right and a vMax method probably is the way to go. But when facing more complex problems like vMax=f(driversWeight, horsepower, carsWeight, streetCondition, weatherCondition), where the method attributes are available in other classes I think I won't calculate the vMax everytime I need it. I'd have to do it again and again, I think it's more useful to have it as (transient) attributes in my Car class and a builder seems to be a reasonable way to show that the car is not yet complete.Carlist
K
0

It sounds like you could set vMax in the Constructor as the parameters weight and horsepower are passed into it.

Karnak answered 20/4, 2015 at 10:26 Comment(0)
O
0

If you are dealing with more and more properties i would suggest go with Properties design pattern and prototype based Java Programming. Else, I would design like below, calculate vMax in the constructor itself, so that it is not required to validate for null every time you access.

class Car {
    private String color;
    private double weight;
    private double horsePower;

    Car(String color, double weight, double horsePower){
     // other fields
     calculateVMax();
    }

    private void calculateVMax() {
        double vMax = weight/horsepower;
        setVMax(vMax); // initialize vMax
    }
    // setters and getters for vMax, color, weight and horse power
}

If it is not one time, then probably having a separate method is the best approach. Mostly, getVMax will always attempt to calculate the vMax as mentioned below.

public double getVMax(){
  return (double) (weight/horsePower);
}
Opacity answered 20/4, 2015 at 10:31 Comment(0)
I
0

You can simply calculate the vMax value in your constructor, like this:

class Car {
    private String color;
    private double weight;
    private double horsePower;
    private double vMax;

    public Car(){
       super();
    }

    public Car(String color, double weight, double horsePower){
       this.color = color;
       this.weight = weight;
       this.horsePower = horsePower;
       if(horsePower!=0) {
          this.vMax = weight / horsePower;
       } else {
          this.vMax = 0;
       }
    }

    // setters and getters 
}

And make sure you check that horsePower value is not zero.

Ilsa answered 20/4, 2015 at 10:41 Comment(0)
R
0

Following on from Maks Builder, a little more fluent...:

import org.apache.commons.lang3.Validate;

class Car {
    private String color;
    private double weight;
    private double horsePower;
    private double vMax;

    // setters and getters

    public String getColor() {
        return color;
    }

    public double getWeight() {
        return weight;
    }

    public double getHorsePower() {
        return horsePower;
    }

    public double getvMax() {
        return vMax;
    }   

    private void validate() {
        Validate.isTrue(!StringUtils.isBlank(color), "color may not be blank");
        Validate.isTrue(weight > 0L, "weight should be set");
        Validate.isTrue(horsePower > 0L, "horsePower should be set");

        if(horsePower!=0) {
            this.vMax = weight / horsePower;
         } else {
            this.vMax=0;
         }        
    }

    private Car(Builder builder) {
        this.color = builder.color;
        this.weight = builder.weight;
        this.horsePower = builder.horsePower;           
    }

    public static class Builder {

        private String color;
        private double weight;
        private double horsePower;

        public Builder withColor(String color) {
            this.color = color;
            return this;
        }

        public Builder withWeight(double weight) {
            this.weight = weight;
            return this;
        }

        public Builder withHorsePower(double horsePower) {
            this.horsePower = horsePower;
            return this;
        }             

        public Car build() {
            Car newCar = new Car(this);
            newCar.validate();
            return newCar;
        }

    }

}

Then simply:

Car car = new Car.Builder()
            .withColor("blue")
            .withHorsepower(10)
            .setWeight(1000)
            .build();

If you are using eclipse, See Bob the Builder: https://code.google.com/a/eclipselabs.org/p/bob-the-builder/

Please note, I have moved the validate method in to the Car class as I believe this makes a more thread safe version. Bob will generate it on the Builder.

Retrograde answered 20/4, 2015 at 10:58 Comment(0)
E
0

The builder pattern would be fitting in this scenario but since you speak of OOP it's perfectly fine to add more properties in a class at later point of time as & when required. Say for properties like vMax=weight/horsepower you can write a method & make vMax available to code outside class via getter method.

public void setVMax(int weight, int horsepower){
vMax=weight/horsepower;
}

You can initially create your class object with certain required properties by initializing them in the constructor.

public Car (String color, double weight, double horsePower) {
        this.color = color;
        this.weight= weight;
        this.horsePower= horsePower;
    }

You can write overloaded constructors but the builder pattern would be a much cleaner solution. Too many overloaded constructors won't look good.

Overloaded constructor say you want a car length property to create an object at a later point of time

public Car (String color, double weight, double horsePower, int length) {
        this.color = color;
        this.weight= weight;
        this.horsePower= horsePower;
        this.length=length;
        }

In the client class create the object as per your requirement

new Car("Red", 56.78, 789);
new Car("Blue", 67.98, 567, 45);
Esoteric answered 21/4, 2015 at 0:30 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.