Let’s talk about fat models. And by fat models I mean fat model classes. There are barely any fat human models after all, but this is not the case with fat model classes. We have all heard it – “Fat Model/Skinny Controller”. This is what makes good MVC design! All you have to do is put most of your logic in the model classes. But what if your model class becomes too fat?
Model classes represent some data. They are rarely just a POJO and usually hold also some logic to manipulate/search/create that data as well. Frequently we just add more and more code in one class and soon it becomes one fat unmaintainable mess. Big classes are a code smell that usually means you break the Single responsibility principle.
Why can’t our model classes be skinny just like human ones?
What are we humans doing when we need to reduce our body fat? Simple – diet and exercise. Let’s try to apply these to our classes.
Diet
We have a class Person. Over time we added more and in it, here is how it looks our fat Person now.
public class Person { /* a bunch of different constructors with many parameters */ private String title; private String firstName; private String middleName; private String lastName; private int age; private String street; private String city; private int zipCode; private String country; /* many, many getters and setters */ public boolean isAddressValid() { // a lot of code ommited return true; } public String getFullName() { return something_that_connects_title_first_middle_and_last_name; } /* many other methods */ }
Lets put it on a diet. Diet mean we remove stuff, right? Why can’t we just remove all address related stuff and move it its own class. All methods that are also address related will go there as well. In Person are left just references to our new class – Address. Here it is:
public class Address { /* various constructors */ private String street; private String city; private int zipCode; private String country; public boolean isAddressValid() { // a lot of code omitted return true; } /* Will return ISO2 code of the country - like US, DE, FR, BG, etc */ public String getCountryISO2() { return ""; } /* Will return ISO3 code of the country - like US, DE, FR, BG, etc */ public String getCountryISO3() { return ""; } /* some other methods */ }
Much better and smaller. Now we have the small problem with country – we do not know what to store in that String – is it full country name? Or perhaps just the 3 letter code will be enough? What if different parts of my application need different way to represent country? We can move all this in another class named country and let that class worry about those details – all that we need in Address is to operate with the Country class. Same like with Person and Address.
All this makes not just for skinny model – it is also a much more reusable code.
What I showed is essentially the “Primitive Obsession” Code Smell and so far we are fighting it by extracting Value Objects. I think this is the best way to create a skinny model, but there are other things you can extract as well – if you have any complex logic in your model, consider moving it to another class.
Maybe you have a method that will search for a Person with these and that parameters? Consider moving it to a separate PersonQueryService class. Maybe your logic touches and uses other models as well? Definitely move it, it does not belong in a class named Person. You need do enrich or transform a model data so it can be represented better in a view? Move it to a class that will sit between your Model and the place that will need it for representation.
Exercise
Enough with the diet. I have been trying to think how to make class run in the park and failed. But I think I know how to make a class build up some muscle and flexibility. Yoga and bodybuilding? Sounds good.
One topic that I did not mention when I was talking about fat model diet was object creation. Fat models usually posses many attributes, so creating one object is not always easy.
Lets create some Person objects!
Person johnSnow = new Person(); johnSnow.setFirstName("John"); johnSnow.setLastName("Snow"); johnSnow.setAge(14); Person eddardStark = new Person(); eddardStark.setTitle("Lord"); eddardStark.setFirstName("Eddard"); eddardStark.setMiddleName("R"); eddardStark.setLastName("Stark"); eddardStark.setAge(35);
We call a constructor and then, in many steps, set whatever we need to set.
Thats a Java Bean – and its great when you have to just change one property of an object. It is however not the best when we need to create an object from the ground up and set many things. Your fat model now tries to make your other code fat as well.
Alternative is to just use a constructor with all parameters. We are not changing John’s first name anyway – we do not need a set method at all in our case.
Person johnSnow = new Person("John", "Snow", 14); Person eddardStark = new Person("Lord", "Eddard", "R", "Stark", 35);
That’s better. But this is not all of the code – let’s take a look at the Person class and explain constructors there.
public Person(String firstName, String lastName, int age) { setFirstName(firstName); setLastName(lastName); setAge(age); } public Person(String title, String firstName, String middleName, String lastName, int age) { this(firstName, lastName, age); setTitle(title); setMiddleName(middleName); }
We have two constructors – first with all mandatory parameter that define a person – firstName, lastName and age. Second is with all parameters. Everything is good until we have to add nickname as well.
That will add a third constructor. And next time we need to add a parameter we will add another one. Or maybe change all references to so the constructor you need to change. Even if you do it correctly and make sure all constructors reference one, it is still error prone not convenient.
That’s telescopic constructor (anti)pattern. Better way is to use a Builder class.
Person eddardStark = new Person.Builder("Eddard", "Stark", 35).middleName("R").nickname("Ned").title("Lord").build();
And lets see how the code will look for the Person class now.
public class Person { private final String title; private final String firstName; private final String middleName; private final String lastName; private final int age; private final String nickname; public static class Builder { private final String firstName; private final String lastName; private final int age; private String title = ""; private String middleName = ""; private String nickname = ""; public Builder(String firstName, String lastName, int age) { this.firstName = firstName; this.lastName = lastName; this.age = age; } public Builder title(String title) { this.title = title; return this; } public Builder middleName(String middleName) { this.middleName = middleName; return this; } public Builder nickname(String nickname) { this.nickname = nickname; return this; } public Person build() { return new Person(this); } } private Person(Builder builder) { this.title = builder.title; this.firstName = builder.firstName; this.middleName = builder.middleName; this.lastName = builder.lastName; this.age = builder.age; this.nickname = builder.nickname; } /* many other methods */ }
Some will argue that this will actually make the class big. Builder pattern is not very effective if your class has only a few parameters. But flexibility and readability compensate for the bit bigger size. It is much less error-prone and makes immutability trivial – something that is usually a sign of a good design. Think for yourself – what exactly are you changing about a Person?
This is described in much great detail in Effective Java by Joshua Bloch. It is a great book and one I highly recommend.
That’s all about diet and exercise for your fat models. Of course, just like with people, it is better to exercise and watch what your models are eating, so they stay skinny all the time and never get too fat.
Thank you for reading.