When we design software with maintainability in mind, one obvious goal is to keep the unnecessary lines of code to a bare minimum. The less you need to see/read that is not vital to your understanding of the domain model, the better.
You want your domain model to be free from too much implementation knowledge. By implementation knowledge I mean things that do not deal directly with the problem that you are trying to solve. A connection to a database is an example of this. It is an implementation detail that helps you achieve your goal, but is not your actual goal or the goal of your clients.
If this type of knowledge is not abstracted away (Information Hiding), it only serves to detract from understanding of the system. You end up having to keep more of the program in your own memory to solve a particular problem. At a certain point bugs become inevitable because the human memory is actually pretty bad.
What is the Tell Don’t Ask principle?
Very briefly, the Tell Don’t Ask principle states that you should tell an object what to do and not query it for data, and then make decisions based on the answer you get back.
In object oriented languages we like to focus on the messages passing between objects, and sending instructions to an object is better than querying it, and then altering it’s state based on the outcome. The latter is how procedural, highly coupled disasters spring into existence.
This style of Tell Don’t Ask promotes both the encapsulation and cohesiveness of your objects and you end up with a less coupled, more pluggable system. A system that is not tightly coupled is less resistant to change, easier to maintain, and easier to test.
If your code already follows the Law of Demeter (objects only communicate with objects that are close to them on the object graph), it will be easier to apply the Tell Don’t Ask principle.
Here is an example of code that does not follow the Tell Don’t Ask Principle:
class StockManager def self.get_stock_purchased stock_purchased_database_query end def self.get_stock_sold stock_sold_database_query end end class DisplayManager def display stock_purchased = StockManager.get_stock_purchased(product) stock_sold = StockManager.get_stock_sold(product) puts stock_purchased.to_i - stock_sold.to_i end end
DisplayManager knows too much about how the stock on hand is calculated. Information from StockManager has leaked out and is not encapsulated. We also have less abstractions which name and clarify our intentions.
Here is code that follows the Tell Don’t Ask principle. The knowledge of stock is kept inside the object.
class StockManager def self.get_count_on_hand get_stock_purchased - get_stock_sold end private def self.get_stock_purchased stock_purchased_database_query end def self.get_stock_sold stock_sold_database_query end end class DisplayManager def display puts StockManager.get_count_on_hand end end
This is a very basic example of where we put the logic with the data, and it has many benefits. It also promotes Composite Simpler Than the Sum of Its Parts and the Law of Demeter.
The Tell Don’t Ask principle has been known to conflict with the Single Responsibility Principle (which I think ranks higher in value), and objects can grow to become large if not used carefully. As with most principles/laws in software design, this is not set in stone and is also not a silver bullet to solve all your problems.
However, by following the Tell Don’t Ask principle and pushing complexity down to lower levels, you end up with a system that is much easier to reason about.