6

Say that we have a class Car that contains objects of type Wheel

class Wheel {
public:

    void SetFriction(double f) {
        friction = f;
    }

private:
    double friction;
};

class Car {

private:
    std::array<Wheel, 4> _wheels;
}

Now, I want to offer the owner of a Car access to the friction of the Wheels. There are mainly two ways I can think of doing this:

  • Give access to the wheels:

    class Car {
    
        Wheel& GetWheel(int id) {
            return _wheels[id];
        }
    
    private:
        std::array<Wheel, 4> _wheels;
    }
    
    Car beetle;
    beetle.GetWheel(1).SetFriction(0.4);
    
  • Give access to the friction directly:

    class Car {
    
        void SetWheelFriction(int id, double friction) {
            _wheels[id].SetFriction(friction);
        }
    
    private:
        std::array<Wheel, 4> _wheels;
    }
    
    Car beetle;
    beetle.SetWheelFriction(1, 0.4);
    

The problem with the first version is that it gives access to a private member, potentially breaking encapsulation. Maybe a function gets added to Wheel (e.g. "SetManufacturer") which shouldn't be exposed to a Car owner.

On the other side, the second version leads to a lot of functions being defined in Car that are not of its direct responsibility, thus violating the single responsibility principle.

class Car {

    void SetWheelFriction(int id, double friction) {
        _wheels[id].SetFriction(friction);
    }

    double GetEngineWeight() {
        return _engine.GetWeight();
    }

    double SetWindowsColor(Color& c) {
        for(auto& w : _windows){
            w.SetColor(c);
        }
    }

        // And so on...

I strongly lean toward the first option, but I would like to hear someone else's opinion.

Maybe somewhat related: https://en.wikipedia.org/wiki/Entity%E2%80%93component%E2%80%93system

3
  • Once the wheel friction is set (as in instantiation), does it ever have to be changed? What I’m getting at, is can wheel be immutable?
    – keelerjr12
    Dec 22, 2017 at 18:05
  • I think that for the sake of this question it shall be assumed that wheel is mutable
    – Fabio
    Dec 22, 2017 at 18:21
  • 2
    I really don't understand why a car owner should directly access the friction of a wheel. Could you provide a use-case? Without additional information we probably cannot give a useful answer. Dec 22, 2017 at 19:32

6 Answers 6

4

You don't mention what the main responsibility of the Car class is.

In a real application, your Car class wouldn't just represent a physical car. It would represent it in a specific context. And knowing the context is important as it is what will shape your class's public API.

Example Context 1 - Racing Simulations

Let's say, for example, that this context is running simulations to optimise a race car's performance. The public API of your Car class could expose data and methods to tweak performance characteristics of your car. In this context it would be quite reasonable for a Simulation class or a PerformanceEvolver class to use the Car's API to directly tweak Wheel Friction (eg, car.setWheelFriction(WheelsEnum.FrontLeft, coefficient: 0.8).

Let's look at a different context now.

Example Context 2 - BOM

Let's imagine that your Car class is used in the context of a Bill of Materials. The primary responsibility of your Car here is to describe the components required to build it. It will certainly have a reference to four wheels, but that's pretty much the extent of wheel-centric data and behaviour included in the Car public API.

So, if you were interested in the components required to build the Front-Left wheel of the car you wouldn't expect to do something like car.getFrontLeftWheelBillOfMaterials() because that would be a wheel concern rather than a car concern. Instead, you would do something like car.Wheels[WheelsEnum.FrontLeft].GetBillOfMaterials().

The Takeaway:

So, all that to say this:

  • It's not possible to give advice on how to model an API on a contrived example.
  • The shape of your models, their APIs, and their semantics (I hope I'm using that term correctly here), will depend a lot on the specific context.

If you're looking for a hard and fast general rule, then I think you're out of luck.

If you have a specific example you'd like help with then I suggest you add that information to your question.

Merry xmas!

3

Single Responsibility Principle (SRP)

The SRP is not about the functionality offered by a class, but about the reasons to change:

  • Option 1 is compliant with SRP, as a change in the Wheel would not require to change the Car.
  • Option 2 might on contrary propagate needs for change: any change in the Wheel interface decided by some wheeling expert might require a change in the Car interface as well.

Problem with option 1

The main problem with option 1 is that it returns a reference to an object. This reference could be saved by some user of the Car (e.g. taking its address in a pointer) and might then be used later, even if the Wheel was in the meantime replaced.

A safer but heavier approach would be to return a proxy object that would expose the Wheel interface, but would ensure that the wheel is accessed via the Car.

Hidden problem with all the approaches

A problem with your approach is that, despite your attempt to encapsulate the wheels, you assumes that a car has 4 wheels, numbered 0 to 3.

Nevertheless, one could imagine some small futuristic cars with 3 wheels. And nowadays, there are already some big limousines with 6 wheels.

So you should at least offer a function giving the number of wheels. Or better, offer an iterator that allows to iterate between the wheels according to a predefined order.

2
  • I'm aware of the problem of assuming a Car has four wheels, it was just for the sake of simplicity. I like the idea of the proxy. I think returning a reference is not inherently bad, as long as it's clear that ownership is retained by the car (which must "outlive" its wheels).
    – Fabio
    Jan 5, 2018 at 16:31
  • @Fabio, I guess the problem is that everything is a compromise, whether for simplicity or some other factor. In the real world, wheels can outlive (or be detached from) cars, and owners have unlimited access to wheels. In programming, we normally want a simplified set of attributes and behaviours and additional constraints on how they are employed. Asking how a car should be modelled is therefore like asking how long should a piece of string be - the only general answer is "to be determined according to the problem at hand". Even the proxy approach risks inappropriate references being retained
    – Steve
    Jan 5, 2018 at 18:23
0

What is the main goal of encapsulation principle? How will the code look like when all properties and methods will be available for every other part of the code? I expect particular class's properties and methods will be used by many other parts of the system, binding them to that class's elements. What is wrong with such binding? It is wrong because some of these elements may be changed during refactors or maintenance of this particular class. And when you change such element, you have to change all "client" code also ("client code" I mean other code that uses this particular elements of the class). It will be an headache to do so.

So what parts of class should be hidden? At least those which can be changed in the future without changing behaviour of the class. "Should I use Set here or List would be better? Both approaches work", "Will my algorithm use this type/variable or the other?". Such questions should warn you that this part of the class is just an internal implementation, just the way that you choose to achieve behaviour. Hide this part from others, because it may change and you don't want to refactor 50% of application because one variable's type was changed.

I've just realized that following Domain Driven Design ideas helps in decisions what should and what shouldn't be exposed. If the class is an Aggregate Root, you can expose it. If it is just an Entity inside of Aggregate - better not.

Answering your question: what is the probability that your Car will not have Wheels in the future? If it is not high, you can expose Wheels, because probably they will not be changed to something else. But exposing array which holds Wheels could not be a good decision, because after some remodeling maybe you will realize lack of idea of Chassis that collects wheels and have some needed behaviour (I don't claim you will - it is just an example). Speaking in terms of DDD - Wheel seems to be an valid Aggregate Root at current state of the model.

BTW. friction is not a property of a wheel. It depends on wheel itself and the surface the wheel is rolling on. ;)

0

On the other side, the second version leads to a lot of functions being defined in Car that are not of its direct responsibility, thus violating the single responsibility principle.

I disagree with the premise you state above. The single responsibility of the car is to get the driver from point A to point B. That responsibility encapsulates lots of smaller details that the user of the car doesn't need to know about such as how much fuel to send to the engine and when. (The user requests acceleration, but the car decides how to produce it.) The wheels are definitely part of the single responsibility of getting the person to their destination. For that reason, I prefer your second example more, though I agree that users shouldn't need to set the friction of a wheel. Whatever that is supposed to accomplish, I would make an accessor for it, but whether it changed the friction of the wheel or did something else to accomplish the task is up to the class. And writing the interface that way allows you a lot of leeway to change it in the future should a better method arise.

0

One reason to prefer the second approach is to conform to the Law of Demeter. If you choose the first approach then you could end up with these long calls which increases coupling between classes. For example your code could look like region.getDealership().getBestSellingCar().getWheel().setFriction(0.25) whereas if it conformed with the Law of Demeter it'd be something like region.setWheelFrictionOnBestSellingCar(0.25)

In the former case the class has to know about the Dealership class, the Car class, and the Wheel class, so a change in any one of them requires a change in the calling code. In the latter case the code only has to know about the Dealership class. There's also an argument for brevity or readability between the two.

2
  • 2
    I don't think your argument is valid. I find region.getDealership().getBestSellingCar().getWheel().setFriction(0.25) easier to understand. Beside you exploit the structure of your objects to give meaning to the statement, which I find nice
    – Fabio
    Jan 5, 2018 at 16:40
  • @Fabio I edited the answer to mention coupling, which I think is more important than readability, but I didn't mention.
    – Max
    Jan 5, 2018 at 17:52
-1

The general rule is to expose the component objects, and let the user set things like friction and color on them directly. However, all general rules are made to be broken. There are cases where you might want to have functions like SetWheelFriction. The primary case is when a user is not expected to be aware of the details of the Wheel class. Sometimes it's an implementation detail. Other times the component class is intended to be an advanced feature that you tend not to use because the new developers using your Car don't need to know about Wheels until much much later.

One may choose to expose just a few features on the main API. Things like checkTirePressure and checkOilLevel might appear on the Car, while the full API for changing the oil might require accessing the OilPan and OilFilter classes.

These functions are often called "syntactic sugar." They can be very useful from a convenience perspective, but like all sugars, use them in moderation. They can rot your teeth!

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.