Should classes define getters for their dependencies?

Viewed 107

Let's say I have a class car which has a motor.

The class motor is defined like this:

class Motor {
    private String code;
    
    public String getCode() {
        return code;
    }
}

How should the class car be defined, assuming we need to access the motor's code from whoever has access to the car?

class Car {
    private Motor motor;

    public Motor getMotor() {
        return motor;
    }
}

Which allows for this:

Car car = ...
Motor motor = car.getMotor();
String code = motor.getCode();

Or

class Car {
    private Motor motor;

    public String getMotorCode() {
        return motor.getCode();
    }
}

Which allows for this:

Car car = ...
String code = car.getMotorCode();

I assume the second option is best since it follows the Law of Demeter, which the first one doesn't. On the other hand, should the outer class define every single getter from the inner class(es)? Should the motor ever be exposed with a getMotor()?

5 Answers

Yes, since Motor is going to be used by itself without its enclosing Car if it warrants its own class. If it doesn't, then the Car class can store String motorCode in itself. This assumes that Car is a composite model without significant logic, as it seems to be in the example.

It's the anorectic class design of the example that makes it unclear, but in a real world application the Motor class has dozens of fields and composite classes of its own. The Car class can't have methods for all of them.

The Car class may then have methods that aggregate data from different composite fields, or make some often used data easier to access without long car.getMotor().getSomething().getData() chains.

Having this same vehicle-motor-etc. hierarchy in our product (although much fuller), I know that in real world applications Motor will be handled separately. As well as potentially things like Transmission, Tyres and so on. So the end result is a hybrid approach where you provide access to the internal composite elements, since sometimes you need it. The object graph is too big and not interesting enough to warrant a lot of design, so it's a trade-off between encapsulation and development speed.

car.getMotor().getCode() wins.

Why?

  • It’s less code; you don’t have to implement getMotorCode()
  • getMotorCode() breaks Demeter's Law – a car would know about, and be coupled to, what fields a motor has. What if one day there’s a motor that doesn’t have a code?
  • getMotorCode() is a convenience method that adds little value
  • getMotorCode() is not where a coder would expect to find a car’s motor’s code, but car.getMotor().getCode() is
  • getMotorCode() is not industry standard - off hand I cannot recall seeing such a method as getMotorCode() (and I’ve seen a lot of code)

Regarding breaking the Law of Demeter, quoting Wikipedia:

An object a can request a service (call a method) of an object instance b, but object a should not "reach through" object b to access yet another object, c, to request its services. Doing so would mean that object a implicitly requires greater knowledge of object b's internal structure.

This precisely describes what getMotorCode() does.

I see this as rather simple: is Motor mutable or is Motor having some information that should be hidden (i.e. : not exposed to clients/callers)? If any of the answer is yes - simply don't expose Motor directly, but only via the information that is allowed.

Otherwise you risk:

  • for callers to change the internals of Motor (which you might not want)

  • exposing too much (like may be VIN should not be visible for everyone)

@Bohemian♦ got this right. Getter and Setters in Java are used to provide standard ways to return data. Implement Motor this way :

public String getCode() {
    return "Code: " + code;
}

Does your Car class needs to know how to return a code ? No
Let's imagine you implement getMotorCode() in Car, will you duplicate how to return a code ? No
You change the way to return a code like return "C: " + code;, will you change every method like getMotorCode() you've implemented ? No

car.getMotor().getCode() is the standard because each part manages its job.

I think most answers so far didn't mention the most important aspect: your classes should always strive to provide services, not objects.

Whenever you have a getter that returns objects, you make the first step towards breaking "tell, don't ask".

Tell your client code to do something (by invoking services of other objects) instead of forcing the client to retrieve other objects, to then make decisions based on the state of such objects!

Related