Exploring Patterns Comments from Aaron Ridout < aaron@blackcat.co.uk >
It is not often that I find myself with stronger feelings than Francis, but after his excellent article in Overload 27 on 'Exploring Patterns', I feel I must write. Not that I disagree with anything Francis said, but having worked on one large-ish project (20+ developers for 15+ months) the project turned more into source code maintenance, for which the following observations became more and more apparent.
For any non-value based class, I would strongly recommend that all attributes of any class should be private, and accessor functions to Get and Set should be provided but scoped as small as possible, (I.e. Private rather than Protected rather than Public).
Now that we have said that all attributes will have accessor functions, the only place that the attributes must be referenced directly (if at all) is in any constructors and destructors. No other class function should access any attribute directly. The reason for these apparently draconian limitations, is that you can now change the attributes without changing the entire class. I view this as a halfway house to implementing a real Cheshire cat, i.e. a Cheshire cat with only private attributes.
For attributes that are values (and this possibly includes references) you typically end up with the following. I've rolled the code into the class declaration for simplicity of the example, never do this in real life, keep them separate until you can prove that performance is an issue and the inline does save run-time)
class Obj { private: T attrib ; private: // Protected or Public IF required T const GetAttrib() const { return attrib ; } ; void SetAttrib( T newvalue ) { attrib = newvalue ; } ; } ;
If an attribute is a pointer then I think you are going to end up with four accessor functions:
-
Get/Set the pointer
-
Get/Set the de-referenced pointer (or the object)
Furthermore, there are const issues to consider. At this time I have not found the 'one' solution to this problem:
class Obj { private: // I'd rather this were 'T *const' T *p_attrib ; // probably private, // should not need to be more open private: T const *const GetAttribPtr() const { return p_attrib ; } ; void SetAttribPtr( T *newptr ) { p_attrib = newptr ; } ; // Protected or Public IF required private: T const GetAttrib() const { return *p_attrib ; } ; void SetAttrib( T newvalue ) { *p_attrib = newvalue ; } ; };
Another problem I found, on the same project, was that once pointers to objects are used, do not allow some domains to start using references to the same type of objects. This is to reinforce the idea of 'don't mix references and pointers'. I see this as part of what Kevlin was saying at the excellent ACCU Forum, that we must manage our object life times. I think we should include the concept of whether we 'talk' to our object via pointers, references or directly (by-value) as this is another aspect of their exterior design that most software designers do not consider fully.