Comments

02/11/06: Life Without Getters

Q: What's the first rule of Object-Oriented Design?

A: Put the behavior with the data.

Now, this may seem to be a simple enough rule. But if that's so, why do I keep seeing code like this?

foo.getBar().getBaz().process(getBar().getBaf())
"Oh, that's just a violation of the Law of Demeter, which isn't really a law anyway, more like a suggestion." you might say.

or this?

@iterations.each do |iteration|
  velocity = 0
  iteration.stories.each do |story|
    if story.feature?
      velocity = velocity + story.velocity
    end
  end
  iteration.total_velocity = velocity
end
"Oh, you're just complaining about Feature Envy", you may complain. "I hate it when IDEA makes half my code yellow so I turn that option off."

But I have a dream. I dream of a world without getters. A world where Tell, Don't Ask is the law of the land. A world where every reference to instance data is in the class containing that data. Let me be perfectly clear:

Every. Single. Reference.

Inside the Same. Class. As the data.

A world where in addition to the first rule above, the next four rules are:

  1. Put the behavior with the data.
  2. Not *near* the data... *with* the data.
  3. No, with the *data*.
  4. Yes, even with that data.
  5. Good. Now keep putting the behavior with the data.

More below the fold...

In other words, write your programs as a series of isolated, encapsulated objects that send messages to each other but are completely responsible for managing their internal state.

In this world, every time a variable is referenced, it's either:

  1. a local variable
  2. a parameter to the current function
  3. an instance variable of the current class
  4. a global (boo! hiss!) -- this includes "class variables" and constants which I always try to minimize in any case
I've seen sample code like this. I've seen shipped code that approaches this. (Mostly it was written by former Smalltalkers like Ward Cunningham or Rob Mee.) But to my surprise and shame, It occured to me recently that I've never myself written an entire program like this. Let me repeat my shame:

I have never yet written an object-oriented program.

If you're at all like me, this coding style may strike you as vastly annoying. Like most programmers, I got into this game because I'm goal-oriented. I see a problem, I see a bunch of tools strewn around and I pick them up and use them on my way to the goal. That leads to functional programming. Even modular decomposition is an afterthought -- a necessary one, and one I now do almost immediately by applying Extract Method liberally, but an afterthought nonetheless. True OOD is a real stretch.

But that's why this is a dream. It's a challenge. Here are some things that may be difficult to do. (If I ever get a chance to do this for real, I'm sure I'll come up with more.)

Methods with return values.

This is value-judgement land. At some point an object will have to report something about its internal state; when it does, isn't it violating the First Rule and letting its instance data leak out? My advice is, whenever possible, avoid with void: make methods without return values and see if you can accomplish what you want by telling, not asking. When you can't get around this, make sure it's transforming its instance data in some way. If all else fails, try to make sure the data you're returning is immutable (or at least doesn't maintain a live pointer back into the original object's internal state). (Returning live objects is especially dangerous when returning collections.)

Rendering.

Normally view code is written like this:

  public void renderPerson(PrintWriter out, Person person) {
    out.print("<h1>" + person.getName() + "</h1>");
    out.print("Age: " + person.getAge() + "<br>");
    out.print("Sex: " + person.getSex() == Sex.MALE ? "Male" : "Female" + "<br>");
    out.print("Address: ");
    renderAddress(out, person.getAddress());
  }
Leaving aside all nattering about the above code ("Localization! HTML Templates! Dont use + on Strings! Extract a renderSex method! Squawk!") I'm sure you see that this method has Feature Envy since nearly every line refers to "person". But we don't want to just move it to the Person class -- Person is an Entity, not a Component, and it shouldn't know about details of HTML. That would be mixing Model and View and we all agree that's to be avoided.

The solution is to use interfaces to make a render method that's HTML-free:

class Person {
  public interface Renderer {
    public void renderPerson(String name, Sex sex, Address address);
  }

  public void renderTo(Renderer renderer) {
    renderer.renderPerson(getName(), getSex(), getAddress());
  }
}

Then inside your View layer you write:

  class PersonInfoPageRenderer implements Person.Renderer, Address.Renderer {
    PrintWriter out;
    public ToHtmlRenderer(PrintWriter out) { this.out = out; }
    public void renderPerson(String name, Sex sex, Address address) {
      out.print("<h1>" + name + "</h1>");
      out.print("Age: " + age + "<br>");
      out.print("Sex: " + sex == Sex.MALE ? "Male" : "Female" + <br>");
      out.print("Address: ");
      address.renderTo(this);
    }

    public void renderAddress(String streetAddress, String city, String state, ...) {
      ...
    }
  }

  person.renderTo(new PersonInfoPageRenderer(out))
Note that the Address entity exports a renderer interface of its own. You can reuse the same view-specific renderer class if you like.

Yes, I know this requires more typing. What can I say? Java is a verbose language.

(That reminds me of my favorite joke:

Q: Why were the Java programmer's fingers sore?
A: Because Java is a strongly-typed language!
Sorry.)

You could do the same thing in Ruby with fewer lines: just define a "render_person" method and pass in self.

Comments made

Brilliant post. I have a similar post here:

http://moffdub.wordpress.co...

where I dubbed the design you advocate here as JUID - Just Use Interfaces, Dummy. There is one flaw, though, and that is the fact that any old class at all can implement Renderer. Then you'd be exposing state to classes that don't necessarily need it.

I also tend for favor factories with friend-level access to internals for tasks such as re-constitution from persistence, persistence itself, and display on a UI. For other tasks within the domain that require transfer of data between objects, JUID is a pretty good solution. Makes more sense than Phone.whatIsYourAreaCode().
10/05/08 06:47:41
We still use getters one level higher to access fields in the tuples that the combine methods send up. And some computations, like comparing two objects,
still need getters.
12/06/11 22:56:31

Add comment

Sorry, but due to blog comment spam, I have to ask you to create an account before you post a comment. Please log in (using the form on the top right of the page) or click here to create an account: Create an account!