Getters and Setters: a people problem

Greetings ladies and gentleman, it is I, El MoffDo, here to escort you on this excursion into Programming Excellence for the next 1300 words on this, the EIP web-ring.

For no particular reason, I was perusing a Smalltalk tutorial and duly noted this:

Class Date defines an instance method named “year” that answers the value of the instance variable named “year” in the instance of Date to which the message “year” is sent.

So this was Smalltalk, the revered and noble language that really made me “get” what objects were all about, tacitly sanctioning a getter. Narrowing my Google search down, I was able to find an entire page dedicated to the topic.

Somewhere in the back of my mind, in the lofty rafters that house my gray cells, a background process was running that only now got scheduled: why the hell did I write this?:

// PCMemoryAggregationService implements PCMemoryAggregator

private int memoryTotal;

public PCMemoryAggregationService()
{
    this.memoryTotal = 0;
}

int calculateTotalMemoryOf(List listOfPCs)
{
    foreach(pc : listOfPCs)
        pc.contributeMemoryTo(this);

    return this.memoryTotal;
}

void addMemoryToSum(int amountOfMemory)
{
    this.memoryTotal += amountOfMemory;
}

// PC

void contributeMemoryTo(PCMemoryAggregator aggregator)
{
    aggregator.addMemoryToSum(this.memCount);
}

// client code

int totalMemory = new PCMemoryAggregationService().
    calculateTotalMemoryOf({ bedroomPC, hallwayPC });

What?

This was me, not too long ago, gushing over the OO-ness of a solution to a problem using Double Dispatch, a problem whose solution would otherwise use a dreaded accessor method.

Quite simply, this is lame. Never mind the triviality of summing a group of integers. That is meant for illustrative purposes, and it is a mere placeholder for a piece of logic that doesn’t fit in those PC objects.

I’d wager that other solutions, like the Friend Class pattern, and most of those restricted method access patterns, are highly inappropriate in this situation.

Question: What is appropriate for a piece of logic that truly does not fit into an object, and therefore cannot be double-dispatched to another object without the resulting code looking like an over-complicated kludge?

Answer: A getter.

Gasp! Procedural punk!

Hold on a second. Allen Holub has it right:

Can you make massive changes to a class definition—even throw out the whole thing and replace it with a completely different implementation—without impacting any of the code that uses that class’s objects?

This is the one and only question you have to answer.

The Double Dispatch example I cited above is not only a kludge, but it nearly violates a domain-driven design practice of recognizing when logic doesn’t fit into any one object. Those are called SERVICES:

When a significant process or transformation in the domain is not a natural responsibility of an ENTITY or VALUE OBJECT, add an operation to the model as a standalone interface declared as a SERVICE.

Summing up the amount of memory in a group of PC objects does not fit into any one of those objects. It can’t. It is a task that collectively involves them all and must be the responsibility of a third party.

The PCAggregatorService technically adheres to this principle, but what I did was a complicated way of saying pc.getMemoryAmount(). I can’t justify this complexity.

I’ve come to realize that for Double Dispatch to be justified, the type of the input to the service has to be shady at best and unknown at worst.

As the author of the service, you offer public methods that do useful work, and the input, which implements an interface of your choosing, is responsible for formulating what needs to happen in terms of those methods. That formulation occurs when your service dispatches back to the input.

In the phone-vs-phone-number example, this means that instead of Phone offering a void dial(int[] areaCode, int[] number) method, it should offer void dial(int[] sequenceOfNumbers).

Then in Phone‘s void dialOn(Phone) method, you’d have any number of calls to void dial(int[] sequenceOfNumbers), whatever was needed to get the job done.

Back on topic: on top of the excessive complexity, my example doesn’t even pass the test of one of Holub’s main complaints about accessors:

You might store getX()’s return value in a local variable, for example, and that variable type must match the return-value type. If you need to change the way the object is implemented in such a way that the type of X changes, you’re in deep trouble.

If X was an int, but now must be a long, you’ll get 1,000 compile errors. If you incorrectly fix the problem by casting the return value to int, the code will compile cleanly, but it won’t work. (The return value might be truncated.) You must modify the code surrounding each of those 1,000 calls to compensate for the change. I certainly don’t want to do that much work.

Certainly there is no reason not to use a getter, though I’d probably name it whatIsYourMemoryAmount() to distinguish it from a getter that is needed for technical reasons.

“But Moff, but Moff, you’re still subject to Holub’s coupling argument.” Yes, I am.

Holub’s argument that getters expose implementation details is absolutely on the money. He is 100% correct. The way he presented the point, though, was unfortunate.

I argue that a PCAggregatorService that uses a pc.whatIsYourMemoryAmount() method, and that’s it, is still relatively loosely coupled to the implementation details of the PC object. Remember: can I completely throw out the object’s implementation without affecting classes that use it?

The answer is a qualified yes, as long as I can answer with an integer to the question “what is your memory amount?” The reason is that this question has meaning in the problem domain. A PC has an amount of memory. No matter how I internally represent this in the PC object, I still have to answer this question because PCAggregatorService needs it.

Domain objects should be coupled to domain concepts; this is the entire point of an isolated domain layer.

And just so we’re clear, if the getter returns an object, you should always clone it before you return it to the caller.

Part of me believes that the entire argument is tenuous at best. Holub would have me believe that his ideal solution would be to return some kind of Integer object that had an Integer add(Integer) method on it. That way, I could have a getter return this and tell the Integers to add themselves up.

Aside from using a full-fledged object to represent a number, this seems reasonable, but at some point — at some point — the data has to flow out in order to be useful. And if an Integer changes how it represents the number internally, then at some point, someone else’s type will need to change too.

Oh and by the way, I don’t buy Holub’s export-everything-as-a-String argument either.

“But Moff, but Moff, does this mean you provide getters and setters for every domain-meaningful attribute?” No. First of all, no (public) setters. You can still offer public ways to alter state that are domain-meaningful. Second of all, you only write these methods as you need them.

“But Moff, but Moff, what about factories, repositories, and export factories?” Easy — they live in the same package as your entity object, and you give package-private getters and setters for every attribute.

“But Moff, but Moff, now they’re tightly coupled to the implementation of the entity!” As they should be! By their very nature, factories, repositories, and export factories have to be tightly coupled to their relevant entity in order to do their work.

Behavior can only go into an object when it fits. When does it fit? If you follow the use case of your system, adhere to Evans’ reminder about Services, follow SRP, and diligently refactor appropriately, you can’t go wrong. And frankly, you have a slightly better time coding when you don’t scowl at each and every getter you call in a third-party library.

The competing problems of maintaining low complexity, encapsulation, and loose coupling is a people problem. Naturally, as a developer, I rebelled against this. That is why I became a developer.

The result was a page dedicated to solutions to the problem that would avoid people as much as possible. That was unfortunate, because people are dumb, panicky dangerous animals and you know it.

Announcer: You’re reading the EIP web-ring.
About these ads

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Follow

Get every new post delivered to your Inbox.

%d bloggers like this: