Tell, Don’t Ask

In this post I’m going to look at the principle of Tell, Don’t Ask. The reason I wanted to write this was because for the longest time the naming of it confused me. I’d see objects asking things of other objects all the time and for whatever reason it was a square peg round hole situation for me. It wasn’t until I learned about the Feature Envy code smell and had watched Allen Holub’s Java Fundamentals: Object-oriented Design course on Pluralsight that it became a little clearer. It seems Tell, Don’t Ask is the practice that prevents Feature Envy. It says that objects shouldn’t burden themselves with the knowledge of other object’s internals in order to accomplish what they want. Put more succinctly:

The object that has the data does the work. – Allen Holub

I think that’s one of the best quotes about OO design I’ve seen, which was from the Allen Holub course on Pluralsight mentioned above. It’s a deceptively simple statement but as soon as I started viewing designs through the lens of “the object that has the data does the work” I started noticing things I hadn’t previously and found myself questioning my own designs more. There’s also another good quote I saw from Steve Freeman and Nat Pryce in Growing Object Oriented Software Guided by Tests:

Ask the question we really want answered, instead of asking for the information to help us figure out the answer ourselves. – Growing Object Oriented Software Guided by Tests

Not asking (“don’t ask”) doesn’t mean you can’t call query methods on an object. Telling another object you want the output of one of it’s methods is fine. Yes this could technically be viewed as a form of asking, but the distinction lies in that the client has no knowledge of how the answer came to be. In other words, how the answer was created was hidden away from the client behind the veil of the collaborators public interface. Maybe the collaborator holds the state that was required to create the answer, maybe the collaborator delegated some part of the answers creation to one of it’s own collaborators, the client shouldn’t know or care. In this case all the client knows of its collaborator is the public interface that it presents to it.

When objects expose their internal state they invite clients to consume it. For example, if we have an object who we’ll call the subject, that exposes all of its state and has 3 clients who all use that state in different ways then we end up with a coupling issue. If we want to make a change to the subject we have 3 clients to consider who may be impacted. To mitigate the risk of changes rippling through the code we could constrain clients by having them communicate with the subject via method calls. This allows the subject to collaborate with its clients but without them knowing about the internals of the subject itself. In this situation it’s easier to change the design or internal structure of the subject, as long as the public contract stays the same we can change its implementation and clients will be non the wiser. Conversely, if clients are dependent on the internal state of the subject and we decide we want to make a change to its design then we have a bigger maintenance issue to deal with as clients may need to change too.

That’s a bit wordy so let’s look at some code and images illustrating it. Below we have two objects, the client who is the object that requires the help of another object and the collaborator who is the object that can provide that help.

Above the black circles represent the public interfaces of each object. This is the boundary that other objects shouldn’t be able to see past, they can see up to it but what happens inside the circle is a mystery to it, it’s a black box.

 

Above we can see that collaborator has some internal state which is represented by the coloured shapes. Here we still have a happy situation as client can only see the public interface of the collaborator and it has no ideas that it holds the shapes within.

 

Now we have a situation where client can see the shapes inside of collaborator, it can see its internal state. A client shouldn’t know about the internal structure of a collaborator. In this example the coloured shapes would be exposed as public properties on the collaborator in a statically typed language like C# or Java.

Naturally there are exemptions to this, for example Value Objects which are used to represent a measure or description of something, see Ward Cunningham’s blog for discussion on Value Objects here. However I’ve also seen in Vladimir Khorikov’s Domain-Driven Design in Practice course that it’s encouraged to have rich, immutable value objects. Everyones context will be slightly different so it’s best to be deliberate and pragmatic rather than dogmatic. Ultimately if you have lots of anaemic objects and you’re smelling a lot of Feature Envy then it’s a good indicator that you have misplaced behaviour.

 

 

Now originally client wanted some help but in this situation it could see the internals of collaborator so it circumvented its public interface and has used its state directly in order to do the work itself. Maybe there was no appropriate method on collaborator so client decided to take the state and implement the work, or even worse maybe there was an appropriate method and now client has duplicated rework of what collaborator could have done. Either way this leads to a break in encapsulation as client is asking for information from collaborator in order to do some work that collaborator could have done for it. The object that has the data does the work.

This is also the situation where you see Feature Envy. In this example client is more interested in the state of collaborator than its own internal state. The appropriate refactoring is to move the envied behaviour onto the envied class, so the behaviour implemented in client would be moved over to collaborator.

Let’s look at that in some code examples. Note that this is all arbitrary so don’t pay too much attention to the details of what the code is actually doing, it’s the Feature Envy in Client’s doSomethingElse method we’ll focus on. In the Client class below there are two methods, the doSomething method makes use of all of Client’s fields however the doSomethingElse method doesn’t do anything with any of Client’s fields rather it’s very interested in Collaborator. Client is asking and not telling.

// all of collaborator's properties are publicly exposed
public class Collaborator {
    public int property1;
    public int property2;
    public int property3;
    
    public int doSomething() {
       return (property3 * 100) + (property1 - property2);
    }
}

public class Client {
    private final Collaborator collaborator;
    private int clientInternalDetail1;
    private int clientInternalDetail2;

    public Client(Collaborator collaborator) {
        this.collaborator = collaborator;
        clientInternalDetail1 = 10;
        clientInternalDetail2 = 20;
    }

    // this method makes use of client's internal state
    public int doSomething() {
        return clientInternalDetail1 + clientInternalDetail2;
    }

    // this method uses none of client's internal state,
    // but it is interested in all of collaborator's state.
    // client is doing a lot of "asking" when it could be "telling" instead.
    public int doSomethingElse() {
        return (collaborator.property1 * collaborator.property2)
                + collaborator.property3;
    }
}

 

To refactor this we extract the body of doSomethingElse to a new method called calculateSomething with the intention to move the newly extract method onto Collaborator.

public class Client {
    private final Collaborator collaborator;
    private int clientInternalDetail1;
    private int clientInternalDetail2;

    public Client(Collaborator collaborator) {
        this.collaborator = collaborator;
        clientInternalDetail1 = 10;
        clientInternalDetail2 = 20;
    }

    // this method makes use of client's internal state
    public int doSomething() {
        return clientInternalDetail1 + clientInternalDetail2;
    }

    // extract the behaviour that is doing the asking.
    // this will be moved onto collaborator
    public int doSomethingElse() {
        return calculateSomething();
    }

    // next we use move method and move the extracted method to collaborator
    private int calculateSomething() {
        return (collaborator.property1 * collaborator.property2)
                + collaborator.property3;
    }
}

 

The calculateSomething method is then moved onto Collaborator. Client now delegates the doSomethingElse method to Collaborator’s calculateSomething. By doing this Client no longer needs to ask Collaborator for its state in order to do the work, it asks Collaborator to do the work for it.

// now collaborator has 3 private fields and 2 public methods
public class Collaborator {
    private int property1;
    private int property2;
    private int property3;

    public int doSomething() {
       return (property3 * 100) + (property1 - property2);
    }

    public int calculateSomething() {
        return (property1 * property2) + property3;
    }
}

public class Client {
    private final Collaborator collaborator;
    private int clientInternalDetail1;
    private int clientInternalDetail2;

    public Client(Collaborator collaborator) {
        this.collaborator = collaborator;
        clientInternalDetail1 = 10;
        clientInternalDetail2 = 20;
    }

    // this method makes use of client's internal state
    public int doSomething() {
        return clientInternalDetail1 + clientInternalDetail2;
    }

    // now client delegates the calculation to collaborator.
    // it no longer knows about property1, 2 and 3 of collaborator.
    public int doSomethingElse() {
        return collaborator.calculateSomething();
    }
}

 

Now there has been a change to Collaborator, two fields have been removed and the calculateSomething method now delegates to another object called Thing. However Client has not had to change as the public API of Collaborator hasn’t changed, only its internal implementation. In the first code sample Client knew all about the internals of Collaborator and if things were still the same then these changes would have also impacted it.

public class Thing {
    private int property1;
    private int property2;

    public int someOtherCalculation() {
        return property1 + property2;
    }
}

// now collaborator has changed how it does its business
public class Collaborator {
    private Thing thing;
    private int property3;

    public Collaborator(Thing thing) {
        this.thing = thing;
    }

    public int doSomething() {
       return (property3 * 100) + thing.someOtherCalculation();
    }

    public int calculateSomething() {
        return thing.someOtherCalculation() + property3;
    }
}

public class Client {
    private final Collaborator collaborator;
    private int clientInternalDetail1;
    private int clientInternalDetail2;

    public Client(Collaborator collaborator) {
        this.collaborator = collaborator;
        clientInternalDetail1 = 10;
        clientInternalDetail2 = 20;
    }

    public int doSomething() {
        return clientInternalDetail1 + clientInternalDetail2;
    }

    // client knows nothing about the internal change to collaborator
    public int doSomethingElse() {
        return collaborator.calculateSomething();
    }
}

 

In conclusion keep a look out for Feature Envy as it may be a sign that objects are asking and not telling. When you do need to ask, ask a question via a method and not for an object’s internals. Sometimes you may need to ask but use your better judgement, be immutable, pragmatic and not dogmatic.

Share

Leave a Reply

Your email address will not be published. Required fields are marked *

Post comment