Modifying data in a class where they reside is always a good thing. The Tell Don’t Ask principle guides us on how to follow such an approach. Unfortunately, it is widespread to obtain data from object and process it inside some service or manager class. After this article, you will know how to talk with objects and process data inside them instead of pulling their logic out of objects. You will be able to test your new skill with kata exercise.

Benefits of Tell Don’t Ask principle

At the first glance, obeying the Tell Don’t Ask principle might look like a cosmetic change for the code. In fact, it provides a lot more than just a simpler, less coupled clean code. By following this principle, you will follow OOP with ease by:

  • The localisation of information — everything related to the object will be kept in one place — its data, ways of modifying it, its flow and ways of processing it
  • Limited code duplication, as operations with objects data will be kept in one place. You will not have to keep an eye on DRY principle, as it will be implemented naturally
  • Strong object-oriented approach, with a focus on abstraction
  • Simplified unit tests. Instead of preparing heavy service classes to test data processing, most of your code could be tested with lightweight unit tests

A little bit of theory

The Tell Don’t Ask is about keeping the data where it belongs – encapsulated inside the object. Encapsulation should prevent us from modifying object internal state without it knowing that. Modifying the object state with setter does not guarantee that, as such the object lacks abstraction. Service classes digging in objects internals are like trying to eat by putting food directly to your stomach, instead of putting the food into your mount.

Sample application used here will simulate a small farm, that has only a few responsibilities. The first task that it will fulfil is to water plants when they need that. The second will be gathering products from the plant, when they are ready, and the last one is to deliver packed products to the customer.

Diagram showing the interaction between service and classes that are violating OOP.
Sample service managing the data kept inside the objects.

With such an approach, we do not give objects any privacy. Tearing out their internals violates OOP and complicates the service logic. Let’s think how we can refactor it to obey Tell Don’t Ask principle and achieve clean code. Try to find the logic that has been taken out of classes. Spot for the places, where some decision is made. Found it? The first step would be to tell the Plant to water it using some WaterValve, instead of service doing that.

Diagram showing the interaction between service and classes while using Tell Don't Ask Principle.
Tell don’t ask principle used to refactor code to more object oriented one.

By telling the Plant to water it with some WaterValve, we encapsulated its logic inside the class. We do not need to make condition outside of the Plant class, which simplifies service testing by reducing the number of interactions between objects and possible execution paths. Let’s take a look into the practice.

Modeling it in code

Let’s look at the implementation that reflects the first diagram. It is digging deeply into objects internal, fetching plants from RaisedBed and getting internal properties from those plants. If that wasn’t enough, it is implementing plant-related logic, asking the plant for its values and setting new ones, depending on the Plant state. Service has huge knowledge about other object internals.

Sample service implementation that does not follow the Tell Don’t Ask principle.

Apart from asking for internal values, it also differentiates the logic depending on the type. Think a little bit about that, how you are going to test this solution? Are you going to check logic for every type of plant? Will you make different tests depending on plant soil moisture? What if there will be no raised bed or valve? Testing every possible path will require a lot of tests. Or, maybe you will decide risk bugs and not test everything?

Getting hands dirty

Consider the same service, but respecting the Tell Don’t Ask principle. Let’s literally tell the raised bed to be watered with some valve, and let it delegate this work to its plants.

Watering service after we applied the Tell Don’t Ask principle into it.

When we applied to Tell Don’t Ask principle, this service simply delegates the request to it. Its only responsibility is to orchestrate invocation and manage the repositories. Testing it will only limit to the integration with repositories, while logic that used to be in it can be checked easily with unit tests. You might struggle, where the logic is now? Take look on a part of refactored RaisedBed.

Again, using Tell Don’t Ask, we are not using the internals of Plant in here – we are only managing specific plant watering, but we let the plant to decide if it needs one. Again, there are not many cases to test in here, so writing unit test should be easy. Next step with this refactoring will be to include the watering logic inside the plant itself. You can consider introducing a Strategy pattern to simplify logic even more.

Dealing with dependencies

Sometimes, to perform an action, you will need to call another service. In our application, this is a situation when we sale products. Plants after gathering are organized into crates. Crates can be packed for the customer, and such a package can be sent to it. However, sending a package is managed by another service.

Service with dependent service usage without refactoring.

There are two most common approaches to refactor that into code obeying Tell Don’t Ask principle.

Service orchestrating invocation

First discussed approach relays on a service (Sales in this case), that has the knowledge about that when the Package should be sent. Sales service is an orchestrator for the process. In this case, we simply pass the package to delivery service so it will be processed by it.

Service orchestrating invocation of another service.

For most people, it is natural to code this way, so the benefit of this approach is the ease of understanding this solution. On the other hand, it creates a little bit of coupling between Sales service and a Package class – Sales does need to know when packaging was successful, and when it should be delivered.

Disconnected Domain Model variation

Disconnected Domain Model is was described in Vernon’s “Implementing Domain-Driven Design” book as a technique of injecting repositories into aggregates. Injecting repositories in every aggregate by constructor may cause problems with dependency injection mechanisms due to a large number of aggregates that should be fed with repository dependency. Instead of that, we might try to pass this dependency directly that need to use some service.

packageToDeliver.send() requires DeliveryService to work.

This technique limits the knowledge of Sales service about Package class implementation, so it simplifies testing of a service. It keeps the whole logic related to Package inside class. On the other hand, it usually brings confusion to the developer, who is unfamiliar with it. While using this technique, carefully select services that are injected. Using those services only for invoking processing is usually not a threat, but using a value returned from injected service might lead to Flag Argument antipattern.

Summary

The Tell Don’t Ask principle promotes keeping logic inside object instead of service. This increases their cohesion, making the code more maintainable and easier to test. Extracting the logic into service makes them unnecessarily big and complex. Try to speak with your objects, and tell them what to do.

Code Kata

If you feel, like you need some practice in adapting code according to Tell Don’t Ask principle, challenge yourself to do related Code Kata. You will find the description in the Readme file. Try to write as much as you can by yourself. There are some unit tests already written, but you will need much more of those. Sample solution can be found on this branch, but remember, that it is just a sample, and there are multiple ways of doing it.

Featured image by Pixabay from Pexels.

Share this post on