3

Interesting question came up while designing interfaces at work, now resolved, but I want to ask about the theory behind it.

Is it incorrect to say that properly typed data members of a class provide encapsulation? (e.g. a Boost Units type that has conversions well defined between other like units, not a typedef'd/boxed uint64_t)

struct Ruler
{
    Length length;
    Length tick_size;   

    Ruler(Length length, Length tick_size);
    // Why not have helpers immediately related to the class, let's
    // stick in an alternative constructor
    Ruler(Length length, int number_ticks); 

    // Accessors for # tick marks, because it needs transformed 
    // to/from length and tick_size
    int GetNumberTicks(void); 
    // This specific example breaks down with this function, 
    // but I don't think it's an inherent issue with the design.
    // I need an overload so it will know which of the two member 
    // variables to calculate... problem is both are Length
    void SetNumberTicks(int nticks); 
}

vs

struct Ruler
{        
    Ruler(Length length, Length tick_size);
    Ruler(Length length, int number_ticks); 

    // General accessors
    Length GetLength(void);
    void SetLength(Length length);

    Length GetTickSize(void);
    void SetTickSize(Length length);

    // Calculated accessors, see above
    int GetNumberTicks(void); 
    void SetNumberTicks(int nticks); 

private:
    Length length;
    Length tick_size;   
}

IMO the first encourages cleaner design by consumers and doesn't encourage pulling in slightly related, but probably attached to the wrong object member functions (eg CanMachineCreateRuler() ). I don't really have the words to describe this properly and I may be misunderstanding the additional utility that the accessors/private data combination provide.

5 Answers 5

2

Accessors for basic data types generally come from the additional operators available to those types. While getting and setting variables may be allowed, keeping extra references to those variables is often discouraged.

Some languages also need accessors when the encapsulated program wants to make changes at the time an accessor is used, e.g., computing a lazy value or invalidating a cache.

Much of this can be solved by creating general accessor types, such as "an int, that has been initialized, for which no address can be obtained, which is not a NaN, is signed, and, upon overflow takes the largest absolute value allowed." or "an int, that has been initialized, which is arbitrary meaning no arithmetic operations are allowed, for which references can be taken." The choice is often to trade off the pain of specialized types versus the pain of dependency injection.

3

The second one is completely useless- the setters do nothing but allow the user to do what he could already do, namely mutate the instance. He can simply assign a new Ruler to it at any time.

Also, those computed getters don't really mean much as members, they should really be free functions.

Fundamentally, Ruler has no hidden state, so it's just a helper and the design doesn't really matter that much- the interface should just be as clean and simple as possible, so I would favour the first.

0

Yes, if the public member variables are independent of each other. It does break a uniform convention of using accessors in other classes when justified, however, which is probably enough reason to stick with accessors with regards to code style.

0

I don't really have the words to describe this properly and I may be misunderstanding the additional utility that the accessors/private data combination provide.

Let's say that tick size should never be zero, as GetNumberTicks would have a divide by zero issue.

Now, can we generalize this to say that Length should never be zero, allowing us to perform the check inside of Length itself, or is this specific to the context of a Ruler? The answer should be obvious.

This is one of the reasons why accessors are often needed even if you have rich data types that don't expose raw state, e.g. They need that contextual, site-specific information (Ruler-specific info here). They often need to consider the invariants maintained by the outer class (Ruler), and possibly even the interaction of multiple data members (ex: "this Length should never be bigger than this other Length in this class").

A single, scalar-type field often lacks sufficient information to provide the most meaningful accessors/mutators. We often need the contextual info of the class aggregating these fields.

Another reason is this: let's say you find a new way to represent Length which is half the size (making Ruler more cache-friendly with no loss of needed information) but you have 10,000 places in your code that depended on that GetLength accessor. The new type is more efficient, but can easily be converted to/from Length on demand. In this case, thankfully you can more easily apply this under the hood optimization to the private fields of Ruler without rewriting 10,000 lines of code because of GetLength/SetLength. If you exposed the Length fields publicly, you would have to rewrite all 10,000 lines of code to make this change.

Now I'm personally more of a C-style implementor and C++-style interface designer, so for something like this, I'd often just go with this (warning: not recommended for most):

struct Ruler
{
    Length length;
    Length tick_size;   
};

... it's like whatever, I don't care about utmost safety here since I'd probably use this thing in some much more complex outer context which can impose the safety checks. I'll then do a lot of testing on the places where Ruler is used. This is unless Ruler was more than a localized implementation detail and had a large number of efferent couplings (in which case I might do the full-blown private fields and getter/setter style design with at least safety assertions to check to make sure tick_size isn't zero in both constructor and setter).

But this is why accessors are important in an outer aggregate like Ruler even if its individual fields also provide accessors.

Also, just a small FYI, but your Get* member functions could benefit from some const-correctness.

0

Too many accessors are a code smell. They can mean one of two things:

  1. Modeling of data instead of behavior: There are cases where this is adequate. However, I believe you should be honest about it when you are modeling data: just use public members in this case.

  2. Failure to abstract: Ideally every member function should actually do something valuable. It should abstract away from the details how the class is implemented (what data members it uses), and provide some higher level functionality that is actually required by the user.

    Providing only simple accessors leaves all the logic that work on its data in the calling code. This is as much a violation of the encapsulation principle as public data members.

Simple getters may be a good idea, because they provide read-only access to their data member. But once you have both a trivial setter and a trivial getter, you know that something's wrong.

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.