Luchian Grigore
Luchian Grigore

Reputation: 258618

How to work around a getter that also sets a member?

I'm dealing with something that goes along these lines:

I have a class that is quite complex and a member that depends on some stuff that isn't set when the class is initialized, or is set on the go. Objects of that class make sense even when that member isn't set. It can also be reset depending on other changes that are made to other members.

Now, assume this "special" member is computationally-expensive to set, and so I'm delaying computing it on request.

So:

class Class
{
    X x;
    Y y;
    SpecialClass specialObject;
public:
    void setX(const X& newX);
    void setY(const Y& newY);

    //----
    SpecialClass getSpecialObject() /*const*/
    { 
        computeSpecialObject();
        return specialObject();
    }
private:
    void computeSpecialObject()
    {
        //specialObject depends on x and y
        //and is expensive to compute
        //this method is a bottleneck
    }
};

I don't want to call the compute method every time I change x or y because it's expensive, so I'm left with the dilemma:

Is there a design pattern that deals with this? A good approach? Or is the class design just wrong? (I'd lean towards this last one, but changing the class isn't really an option)

Note: I've made the member mutable, want to know if there's a better solution.

Upvotes: 4

Views: 380

Answers (4)

Open AI - Opting Out
Open AI - Opting Out

Reputation: 24133

There are two directions you could go, more OOP or more Functional. One involves caring less about state manipulation, but rather behaviour, the other completely forgets about behaviour, and cares about returned state.

OOP

For me a key OOP principle is Tell, Don't Ask, or write no getters or setters.

Design your objects to be told what to do, to be autonomous. Don't ask it to return some object which you can then use to do something. Just tell it to do the thing you want in the first place. If you're telling an object to do something then you likely expect it to change state, and it's not right for it to be const.

Your SpecialClass may provide some service doService(). You can instead tell Class to doSpecialService(), which is rightly mutable.

An alternative is for the creation of this object to use some other object to do the creation. So a function can be const but take a non const parameter:

class Class {
public:
    void doService(ServiceProvider& serviceProvider) const {
        serviceProvider.doService(x, y);
    }
};

With this you would pass in a SpecialServiceProvider& which would create the correct SpecialClass for the given X and Y. It would be mutable. It would seem correct to modify state in the provision of the service. Maybe you could have a map caching SpecialClass objects for (X, Y) pairs.

Functional

The other direction is to make your objects immutable. Whenever you want some new state create it using the old state as a basis. This could have a knock on effect until you have turtles (almost) all the way down:

class SpecialBuilder {
public:
    SpecialBuilder withX(const X& newX) const;
    SpecialBuilder withY(const Y& newY) const;
    SpecialClass build() const;
};

SpecialBuilder specialBuilder;
SpecialClass special = specialBuilder.withX(x).withY(y).build();

You can share data between each returned SpecialBuilder as it is immutable.

Upvotes: 0

Tony Hopkinson
Tony Hopkinson

Reputation: 20320

It's always a fine line this one, not calling it when you don't need to, versus introduces holes in caller, that mean it might not have been and returning incorrect results.

Me I'd move compute to be method of special object and the treat this class as a wrapper for the arguments for the compute method that class. A bonus ball is you can unit test the computation.

Then it's just a question of deciding when you need to call SpecialObject.Compute(x,y) again or simply return the last result. Another thing I might look at if I could, would be if X has changed but Y hasn't can I simplify the calculation. i.e. keep some intermediate results.

Not sure how applicable it is for you, but one of the things I regularly end up doing is injecting a something that does the compute, so I tend to fall into this pattern by default.

Upvotes: 0

Andy Prowl
Andy Prowl

Reputation: 126462

I can make specialObject mutable, but it doesn't seem like the right thing to do.

Why so? That's exactly why mutable exists: to allow a const function to be logically const without the need to physically leave the object unchanged (and if you make the object mutable, remember about ensuring thread-safety - I'm sure you know what I mean).

This is true as long as the initialization of the SpecialClass object is not something that alters the logical state of the object, of course, because that's what const promises not to do.

In that case, the function itself is simply not const in nature, and it should likely be named something different than just getSpecialObject(): computeAndReturnSpecialObject() could be a candidate.

Upvotes: 4

Nik Bougalis
Nik Bougalis

Reputation: 10613

I'd leave the const and either make specialObject mutable or keep a pointer to the specialObject instead of just 'embedding' it into the class.

I would also add a bool dirty flag that is mutable and set it whenever a change is made that invalidates the computation. I would then check the flag inside computeSpecialObject and do the work only if it's set. Using a pointer, you could even delete the old computation object whenever a change invalidates an existing computation, but that opens a whole 'nother can of worms.

Or am I missing something?

Upvotes: 3

Related Questions