Tip Your Hat To Accessors And Mutators

March 12, 2008. Filed under object-oriented 1

I remember when I was first learning to program as an undergraduate, and the teachers mentioning that we should always use mutators and accessors to retrieve and modify data in our object oriented code. Of course, we called them setter and getter methods, and most of the students hated them. I remember one student in particular who refused to use them, because they were "stupid". As the story goes, he is--of course--now a professional programmer.

I think that many new programmers get the wrong idea about accessors and mutators, and I think its because there are few clear explanations about why anyone should care. Professors may mention that students should always use setter/getter pairs to handle interaction with their data, but I never heard an explanation for why they were important. When you are learning to program you are being overloaded with new information, and the brain is going to throw away stuff it doesn't understand or thinks is irrelevant. The student who thought they were stupid made the argument that they are just cruft from tradition, and not worth the lines of code they are written on.

However, the longer I program the more I see the delicate value of mutator and accessor methods.

Lazy Initialization

The first time I saw value in an accessor method was figuring out how to use Java's Swing graphics package. The class was using Visual Eclipse, which generates much of the code for you, and I noticed that very few variables were initialized when the app was first run, but instead most things were initialized in their accessor.

Instead of the pointless accessor of yore:

public JList getPersonList() {
    return personList;
}

I started to see accessors that actually did something:

public JList getPersonList() {
    if (personList == null) {
        personList = new JList(getPersons());
        personList.setCellRenderer(getMyCustomRenderer());
        personList.setPrototypeCellValue("Initial");
        personList.setPreferredSize(new java.awt.Dimension(100,200));
    }
    return personList;
}

All of the sudden the value of an accessor became a lot more clear. You can use it to initialize variables in your object, but only when you actually need them. If you only use a certain variable in a rare situation, thats fine, it won't get initialized unless you are actually in that rare situation. Or perhaps you have a lot of computationally demanding calculations, but you only need a portion of the results at any given time, thats fine too, because lazy initialization--the first boon of the accessor--will help defray those costs.

In more advanced problems you can implement a dynamic programming algorithm to generate and cache only the values you are actually interested in, and hide it all behind an accessor:

- (id) valueAtX: (int)x andY: (int)y {
    id val = [self cachedValueForX:x andY:y];
    if (val == nil) {
        // use dynamic programming to generate the value
    }
    return val;
}

This can be a great way of boosting performance without adding any complexity to the classes public API.

Access Control

Another usage of accessors is to verify that an accessing object is allowed to use a certain accessor. Perhaps you only want an associated master object to be able to use your accessor methods:

-(NSNumber *)ssn: (NSObject *)sender {
    if (sender != [self masterObject]) return nil;
    return ssn;
}

A pretty simple example, but you can extrapolate it to more useful situations. For example distributed objects that are being shared across multiple programs, and certain data needs to be protected from being accessed too freely.

Mutators

Okay, so we've look at a few reasons why accessors are important, and now lets consider mutators. The arguments for mutators are a bit more self-apparent, but worth examining anyway.

Data Validation

Often you want to verify that an update is valid before updating it, and mutators are the perfect place to do that.

def setName(self, newName):
    if len(newName) < 3:
        raise TooShortNameException, u"You name is too short!"
    elif newName in ["Bob", "Jim", "Joe"]:
        raise AwfulNameException, u"The quality of your name is substandard."
    elif newName.contains("teh"):
        raise IHateInternetSpellingException, u"Just go home."
    self.name = newName

Doing Cleanup and Setup

Also, when you use a mutator you can do cleanup on your previous value before updating to the new one. For example you might have a stream to a file that you are writing too frequently, and you need to close it up, along with opening a stream to the new file as well.

def setFile(self, newFile):
    if newFile != self.file:
        self.file.close()
        self.file = newFile
        self.file.open()

This is particularly important for languages that don't have automatic garbage collection, but even collected languges occasionally need some explicit cleanup (breaking a cycle of links, for example).

Inheritance

The real potential of mutators and settors becomes most apparent when you start subclassing your object, and you get to take advantage of the existing validation or setup code, and just add small additional pieces.

For example, you might want the subclass to return the same result as the superclass, but in uppercase instead of lower:

def getName(self):
    return super.getName().upper()

Or you might want to create a subclass that logs its operations.

-(NSString *)name {
    NSString * tmpName = [super name];
    NSLog(@"%@: Setting name to %@.", self, tmpName);
    return tmpName;
}

Or, you might add extra validation to a mutator:

def setName(self, newName):
    if newName.contains(u"McLovin"):
        raise TopicalButPredictableException, "A good try, but..."
    return super.setName(newName)

Some of these uses are less potent than others, but together they form a strong argument for the practice of writing accessors and mutators, beyond the simplest defense that directly accessing an object's members will tie code to implementation details, and break crash through any layers of abstraction.

Am I overvaluing accessors and mutators? Are they only valuable in crufty old languages like Objective C, Java, or C++, but wasteful in the dynamic scripting utoptias of today? I know I used to get upset by them, but as the size and complexity of my projects increases, I find it hard to overlook their helpfulness.