Code Smell : Anemic Model / Data Class
{% raw %}
Problem faced:
During our software development, we used to use design a class just to use it as a data structure. But while i was interested in reading about code smells; i got to know that its one of the anti pattern. So what is the problem with using a class like that; what is the design pattern then ?
Introduction
A key goal of Object Oriented design is to minimise dependencies between classes and components by packaging data and behaviour as close together as we can. In practice, a good rule of thumb for class design is to put fields and methods that use those fields in the same classes. Data classes are classes which just have fields and no behaviour (besides simple getters and setters), and they break this rule of thumb, creating serious dependency issues in your code.
TL;DR:
Don’t use objects as data structures. ( Class with no behaviour/functionality apart from getters and setters )
Explanation
Let’s design a CustomerReviewSummary functionality,
class Customer: def __init__(self, address, title, first_name, last_name): self.__address = address self.__title = title self.__first_name = first_name self.__last_name = last_name def get_title(self): return self.__title def set_title(self, title): self.__title = title def set_first_name(self, first_name): self.__first_name = first_name def get_first_name(self): return self.__first_name def set_last_name(self, last_name): self.__last_name = last_name def get_last_name(self): return self.__last_name def set_address(self, address): self.__address = address def get_address(self): return self.__addressclass Address: def __init__(self, city, post_code, country): self.__city = city self.__post_code = post_code self.__country = country def get_city(self): return self.__city def set_city(self, city): self.__city = city def get_country(self): return self.__country def set_country(self, country): self.__country = country def get_postcode(self): return self.__postcode def set_postcode(self, postcode): self.__postcode = postcode class CustomerSummaryView: def __init__(self, customer): self.customer = customer def get_customer_summary(self): address = self.customer.get_address() return self.customer.get_title() + " " + self.customer.get_first_name() + " " + self.customer.get_last_name() + ", " + address.get_city() + ", " + address.get_post_code() + ", " + address.get_country()
In the above implementation of classes; the class Address and Customer are not having any behaviours. These are used as a database template or like a datastructure. All the logics were residing in the Service CustomerSummaryView.
Let’s See how it could be redesigned with DDD, RDM where the logic/behaviour would reside inside the class itself.
class Customer: def __init__(self, address, title, first_name, last_name): self.__address = address self.__title = title self.__first_name = first_name self.__last_name = last_name def get_title(self): return self.__title def set_title(self, title): self.__title = title def set_first_name(self, first_name): self.__first_name = first_name def get_first_name(self): return self.__first_name def set_last_name(self, last_name): self.__last_name = last_name def get_last_name(self): return self.__last_name def set_address(self, address): self.__address = address def get_address(self): return self.__address def get_customer_summary(self): return self.__title + " " + self.__first_name + " " + self.__last_name + self.__address.get_address_summary()class Address: def __init__(self, city, post_code, country): self.__city = city self.__post_code = post_code self.__country = country def get_city(self): return self.__city def set_city(self, city): self.__city = city def get_country(self): return self.__country def set_country(self, country): self.__country = country def get_postcode(self): return self.__postcode def set_postcode(self, postcode): self.__postcode = postcode def get_address_summary(self): return self.__city + ", " + self.__postcode + ", " + self.__country class CustomerSummaryView: def __init__(self, customer): self.customer = customer def get_customer_summary(self): return self.customer.get_customer_summary()
Solutions
- Find Responsibilities to your class.
- Protect your attributes.
- Hide implementations.
- Delegate
There is nothing right or wrong. It’s with the architect 🙂
Advantages of Anemic Model
- It’s easy to understand. Suppose you’re thinking about how you would store your data in a database. In that case, it becomes very intuitive to create a class that maps to your database schema.
- Easy to implement. You can start quickly because you must think about the data, not the behavior right away.
Final Thoughts
- In Anemic Domain Models (ADM), everything happens in services. Validation, data formatting, everything.
- ADM as consisting of a set of behaviour-free classes containing business data required to model the domain.
- Avoid anemic models.
- Focus always on protocol/behaviour instead of data.
- Behaviour is essential, data is accidental.
- If you want to create a simple CRUD application, maybe an anemic model with a classic MVC framework is enough. But if you want to implement some kind of logic, anemic model means that you will not do object oriented programming.
References:
- codemanship.co.uk/papers.html
- Fowler, Martin. Anaemic Domain Model. martinfowler.com/bliki/AnemicDomainModel.html, 2003.
- blog.inf.ed.ac.uk/sapm/2014/02/04/the-anaem..
{% endraw %}