5

When are Getters and Setters Justified is an excellent question which focuses on using getters and setters as part of the external interface.

What I am struggling with is ... by having getters and setters there is now multiple ways to update the state of the object since private fields can updated directly bypassing the setter or can be updated by the setter.

Take the example below ... _size is the private field and it has two methods getSize() and setSize(int size). If incrementing _size is coded up like the increment method, all is good. On the other hand, using increment2 can put Order into an illegal state.

Is there a way to prevent developers from accidentally bypassing the getter / setter and use the private fields directly!? If not, what convention should be used to ensure that the object is never put into an invalid state?

public class Order {

    private int _size = 0;

    public int getSize() {
        return _size;
    }

    public void setSize(int size) { 
        if (_size < 0)
             throw IllegalArgumentException("...")

        _size = size; 
    }

    public void increment(int increment) {
        setSize(getSize() + increment);
    }

    public void increment2(int increment) {
        _size = _size + increment;
    }



 }
Dakotah North
  • 3,353
  • 1
  • 20
  • 31
  • 2
    "Is there a way to prevent developers from accidentally bypassing the getter / setter and use the private fields directly!?" - "You can make it foolproof, but you can't make it damn-foolproof." - [NAESER'S LAW](http://www.xs4all.nl/~zira/murphy.html) – duros May 22 '11 at 18:43
  • 4
    Programmers that can change the actual source code can mess it up. There is no way to prevent that. – Inca May 22 '11 at 18:54
  • 1
    Maybe introduce a naming convention that mandates all private members are prefixed with _I_SHOULD_NEVER_USE_THIS_DIRECTLY_? – Deckard May 22 '11 at 19:16
  • 2
    C# has the answer which is auto-properties, where it can have a getter and/or a setter, each of which is public/private/protected or internal. That helps a lot with the pass-through properties. In your example, where a getter or a setter can fail, or must perform extra logic/verification, there is no silver bullet. Just try to keep the classes small and hire good coders who are interested in the best practices. Not organizing the logic within the class well is a sign of inexperience, tight deadlines or poor ability. – Job May 22 '11 at 19:47

2 Answers2

6

Getters and setters are for external users of your API

Of course a class can access their internal state and change it as necessary. They don't need to go through a setter. The pattern is designed as a defensive measure for external users of your code. Ideally, the getter provides a clone of the internal state and the setter provides validation to ensure that valid internal state is maintained.

Gary
  • 24,420
  • 9
  • 63
  • 108
2

Firstly, you usually shouldn't have public getters and setters. An object should provide a higher-level interface that doesn't require external objects to be changing, even with validation, the internal state of the object. That way if your object changes other objects shouldn't also need to change.

For this reason, your increment method is a good idea whereas your setSize function is not. You simply shouldn't be providing such a low level interface.

Secondly, the entire object is responsible for maintaining internal state. In your simple case above I'd probably write:

void increment(int amount)
{
     if(amount < 0) throw new InvalidArgumentError("amount must not be negative");
     _size += amount;
}

I think that detecting invalid method arguments is better then checking for state invalidation. That way your external interface focuses on what you are allowed to do with an object instead of how to avoid producing an invalid internal state.

Thirdly, it can make sense to validate your object to make sure no bugs have introduced inconsistencies in it. However, the best way to accomplish that task really depends on the tools that your language gives you. However, I think that trying to use setters for that has problems.

Using setters introduces a lot of additional code to call them. All of your code gets hard to read as a result.

Sometimes invariants depend on two values. For example a C++ std::vector must have its capacity() >= size(). If you try and guard that setSize() and setCapacity(), which one checks that size is greater?

Additional, objects will often become invalid during the processing of internal logic before becoming valid again. A setter checks the object at every assignment, but you really want the check after the publicly called method has completed.

Winston Ewert
  • 24,732
  • 12
  • 72
  • 103