Responsibilities are what differentiate objects. They represent what an object can do for us. And in a good object-oriented system, each object should have correct responsibilities.
Is there any criterion to help us to verify and find them? I think happiness is a good answer.
The common way to define objects is wrong. Objects are not just data plus functions. In other words, objects are not a C file containing structs and related functions. They are much more.
Alan Kay, the OOP inventor, majored in biology. For this reason, the analogies between cells - the smallest unit of life - and objects are far from casual. Objects are inspired by the cells. Objects are inspired by life. Objects are living organisms. And in an object-oriented system, they collaborate to evoke a specific domain. Each object is responsible to do something.
Thanks to this point of view, the happiness definition makes sense:
Happiness is a state of well-being that encompasses living a good life, one with a sense of meaning and deep contentment.
So an object is happy if it does what it should do in the modeled domain. No more, no less.
Objects are in pursuit of happiness.
The majority of object-oriented systems are tragedies. And common practices, considered good, are actually bad. I refer to DTO, ORM, Service objects, etc…
To show the differences I’ll consider a domain model with two points of view. The first one using common practices, the second one using object thinking.
The domain regards a pastry shop showcase. Talking with the pastry shop owner we identified five high priority user stories:
With common practices in mind we usually start with:
interface PastryRepository {
void save(Pastry pastry);
void delete(String name);
List<Pastry> findAll();
}
@Entity
class Pastry {
@Id
private String name;
private Double price;
private List<String> ingredients;
// getters, setters ...
}
interface ShowcaseRepository {
void save(ShowcaseEntry);
List<ShowcaseEntry> findAll();
}
@Entity
class ShowcaseEntry {
@Id
private UUID id;
private Pastry pastry;
private Integer quantity;
// getters, setters ...
}
class PastryService {
// constructor
void sell(Pastry pastry, Integer quantity) {
// sell a pastry
}
void save(Pastry pastry) {
// save a pastry
}
}
According to the previous happiness definition, the Pastry
objects have suicidal thoughts. They’re central in the conversation with the owner, but here they’re underestimated. They're screaming that they can do more for us. They are reclaiming respect.
Furthermore, we defined PastryService
, PastryRepository
, ShowcaseEntry
and ShowcaseRepository
. But the pastry shop owner never mentioned them in our conversation. They’re out of place. In other words, they don’t know their place in the Universe (the domain).
It’s a tragedy.
We can start with:
interface Pastry {
void sell(Integer quantity);
JsonObject description();
}
interface Details {
void track();
JsonObject description();
}
interface Showcase {
void add(JsonObject description, Integer quantity);
JsonArray description();
}
interface Catalog {
void add(JsonObject description);
void remove(CharSequence name);
Optional<Details> details(CharSequence name);
JsonArray description();
}
The first difference is that every object concerns the domain. They aren’t out of place. After all, they were born starting from user stories.
The second difference is that there isn’t any passive object. In the previous example, Pastry
was a DTO without responsibilities. It was nothing more than a Map
or a C struct. It was really sad because from the stories it’s clear that it'd like to sell itself. And with the responsibility to describe itself it knows its role in the Universe (the domain). Analogous reasoning applies to the other objects.
Considering the living organism metaphor, the happiness criterion can help us to identify responsibilities and objects.
If an object has too many responsibilities, it will be under pressure. And this means we missed one or more objects.
If an object hasn't responsibilities (e.g. DTO), we added bad objects that don't reflect the domain.
In conclusion, sad objects don't reflect the modeled domain. The latter should be the only one that drives us while writing object-oriented systems. With this in mind, we can assign correct responsibilities. And ask ourselves:
Is this object happy to have this responsibility in this domain?
If we forgot this point we’ll write tragedies. And common practices commit this crime.
So happy coding and let’s spread happiness!