Monday, July 13, 2015

Tell, Don't Ask -- OO Principles



http://letstalkaboutjava.blogspot.com/2015/10/wheres-law.html
If you would like to describe Law of Demeter in one sentence, it would go like that: “talk only with your (closest) friends”.
In full form it tells that a method of particular object can call only methods that belong to:
  • the same object,
  • any object that is an attribute of this object,
  • any object that was passed as a method’s parameter
  • any object that was locally created.
http://letstalkaboutjava.blogspot.com/2015/11/tell-dont-ask.html

ask for the result, not for data

In short summary, the TDA principle tells us that instead of asking objects for data we should tell them what the should do and then wait for the result of the operation.

Which means that instead of::
?
1
2
3
4
Age age = sebastian.getAge();
if (age >= 18) {
    letDoTheThingsThatAdultsDoes(sebastian);
}

We should write something like this:
?
1
2
3
if (sebastian.isAdult()) {
    letDoTheThingsThatAdultsDoes(sebastian);
}

the more you tell, the bigger objects can become...

The example above very well presents what the TDA principle is suppose to protect. I’m talking about encapsulation. 
We are not pulling out data from the object. We are not letting them be known to the outside world. We are not working on them directly. Instead, we are telling the object what have to be done and we are waiting for the result. 
After all, shouldn't it be an object’s responsibility to operate with its own attributes?

Yet, you cannot be too eager. Encapsulation is important, but it is as well important for an object to have appropriate API. API which allows for comfortable work. 

And what in a situation where a particular operation requires data from a few different objects of different type?
Let’s even simplify the problem! Let’s talk about calculations that need data from different objects of the same class. Simple example can be calculation of average age in group of people, where each person is separate object. 

How should we do it? 

Should we ask for the age:
?
1
2
3
4
5
6
7
Age totalAge = Age.NEWLY_BIRTH;
for (Person person : persons) {
    totalAge = totalAge.add(person.getAge());
}
return totalAge.divide(persons.size());


Or maybe we should start to think how to solve this by telling objects what we want?

summary

As you can see “Tell, don’t ask” principle cannot be treated like the rule of thumb. 
We always should keep in mind encapsulation and think at least twice before we would pull out data from the object to the outer world. However, I hope that this article showed you that sometimes it is necessary.
http://martinfowler.com/bliki/TellDontAsk.html
Tell-Don't-Ask is a principle that helps people remember that object-orientation is about bundling data with the functions that operate on that data. It reminds us that rather than asking an object for data and acting on that data, we should instead tell an object what to do. This encourages to move behavior into an object to go with the data.
One of the fundamental principles of object-oriented design is to combine data and behavior, so that the basic elements of our system (objects) combine both together. 

This is often a good thing because this data and the behavior that manipulates them are tightly coupled: changes in one cause changes in the other, understanding one helps you understand the other.

https://robots.thoughtbot.com/tell-dont-ask
Good OOP is about telling objects what you want done, not querying an object and acting on its behalf. Data and operations that depend on that data belong in the same object.
<% if current_user.admin? %>
  <%= current_user.admin_welcome_message %>
<% else %>
  <%= current_user.user_welcome_message %>
<% end %>
+
Better:
+
<%= current_user.welcome_message %>
Not so good:
+
def check_for_overheating(system_monitor)
  if system_monitor.temperature > 100
    system_monitor.sound_alarms
  end
end
+
Better:
+
system_monitor.check_for_overheating

class SystemMonitor
  def check_for_overheating
    if temperature > 100
      sound_alarms
    end
  end
end
class Post
  def send_to_feed
    if user.is_a?(TwitterUser)
      user.send_to_feed(contents)
    end
  end
end
+
Better:
+
class Post
  def send_to_feed
    user.send_to_feed(contents)
  end
end

class TwitterUser
  def send_to_feed(contents)
    twitter_client.post_to_feed(contents)
  end
end

class EmailUser
  def send_to_feed(contents)
    # no-op.
  end
end
Not so good:
+
def street_name(user)
  if user.address
    user.address.street_name
  else
    'No street name on file'
  end
end
+
Better:
1
def street_name(user)
  user.address.street_name
end

class User
  def address
    @address || NullAddress.new
  end
end

class NullAddress
  def street_name
    'No street name on file'
  end
end
https://pragprog.com/articles/tell-dont-ask
Procedural code gets information then makes decisions. Object-oriented code tells objects to do things.

That is, you should endeavor to tell objects what you want them to do; do not ask them questions about their state, make a decision, and then tell them what to do.
The problem is that, as the caller, you should not be making decisions based on the state of the called object that result in you then changing the state of the object. The logic you are implementing is probably the called object’s responsibility, not yours. 

For you to make decisions outside the object violates its encapsulation.
http://www.mockobjects.com/2006/10/tell-dont-ask-and-mock-objects.html
objects make decisions using only the information that they hold internally or that they receive as message parameters; they do not make decisions using information that is held by other objects. 

That is, objects tell each other what to do by sending commands to one another, they don't ask each other for information and then make decisions upon the results of those queries. 

The extreme opposite of the "Tell, Don't Ask" style is "train-wreck" code that contains lots of statements like "object.getPart().getSubpart().getAttribute()" and "object.getPart().getSubpart().setAttribute(x)". In coding style the implementation of one object is coupled to the structure of its neighbours and its neighbours' neighbours, and so it is difficult to replace its neighbour with one that is implemented differently. 

This style of code is hard to test with Mock Objects. You find yourself creating lots of mock objects that only exist to let the object under test reach the objects that it actually uses. That's a strong sign that the code needs refactoring: you can simplify the code by introducing new methods in the immediate neighbours of the object under test.
http://blog.iamkoch.com/engineering/development/object-oriented-practises/oo/2014/10/13/oo-principles-tell-dont-ask.html
    public MappedObject Convert(Response response) {
        if (ShouldConvertFromBase) // moved from client.
            return ConvertFromBase(response);

        return CreateFrom(response);
    }
Other OO Principles:
Law of Demeter - keep communication to a minimum, don't expose data you don't have to, and keep all logic within the class if possible.
Program to an interface and not an implementation. 

Labels

Review (572) System Design (334) System Design - Review (198) Java (189) Coding (75) Interview-System Design (65) Interview (63) Book Notes (59) Coding - Review (59) to-do (45) Linux (43) Knowledge (39) Interview-Java (35) Knowledge - Review (32) Database (31) Design Patterns (31) Big Data (29) Product Architecture (28) MultiThread (27) Soft Skills (27) Concurrency (26) Cracking Code Interview (26) Miscs (25) Distributed (24) OOD Design (24) Google (23) Career (22) Interview - Review (21) Java - Code (21) Operating System (21) Interview Q&A (20) System Design - Practice (20) Tips (19) Algorithm (17) Company - Facebook (17) Security (17) How to Ace Interview (16) Brain Teaser (14) Linux - Shell (14) Redis (14) Testing (14) Tools (14) Code Quality (13) Search (13) Spark (13) Spring (13) Company - LinkedIn (12) How to (12) Interview-Database (12) Interview-Operating System (12) Solr (12) Architecture Principles (11) Resource (10) Amazon (9) Cache (9) Git (9) Interview - MultiThread (9) Scalability (9) Trouble Shooting (9) Web Dev (9) Architecture Model (8) Better Programmer (8) Cassandra (8) Company - Uber (8) Java67 (8) Math (8) OO Design principles (8) SOLID (8) Design (7) Interview Corner (7) JVM (7) Java Basics (7) Kafka (7) Mac (7) Machine Learning (7) NoSQL (7) C++ (6) Chrome (6) File System (6) Highscalability (6) How to Better (6) Network (6) Restful (6) CareerCup (5) Code Review (5) Hash (5) How to Interview (5) JDK Source Code (5) JavaScript (5) Leetcode (5) Must Known (5) Python (5)

Popular Posts