Background
Two important principle of Object Oriented programming are -
- For each class design aim for low coupling and high cohesion.
- Classes should be open for extension but closed for modification.
We will see how we can use these to design any classes.
Class Design
Lets design a simple Employee class which has it's name.
public class Employee { private String name; public Employee(String name) { this.name = name; } }
Looks good. Every time anyone has to create an instance of Employee he has to supply name in the constructor. So you see any flaw in this?
Well there is. Lets say new requirement comes up and you now have to add employee age. What would you do?
One way would be change the constructor to include age now.
public class Employee { private String name; private int age; public Employee(String name, int age) { this.name = name; this.age = age; } }
Though it solves our new requirement it will break all our existing code. All Users using our existing code will have to make changes now. So we definitely cannot remove the constructor with name in it. So what do we do next ?
Lets create an overridden constructor.
public class Employee { private String name; private int age; public Employee(String name) { this.name = name; } public Employee(String name, int age) { this(name); this.age = age; } }
Ok this looks good. This will not break existing code and will meet our existing requirement. Now take a minute to this what about future requirements. Lets say you may have to add employees sex, salary etc in the Employee mode. What will you do then? Keep adding overloaded constructors?
Sure you can. But that is a very poor design choice. Simplest thing to do is following -
public class Employee { private String name; private int age; public Employee(){} public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } }
That's right. A simple no argument default constructor and getter, setter methods corresponding to instance variables. This is flexible design implementation. Going forward we can add as many instance variables with corresponding get and set methods.
Though this is good and simple class design. I would not say it is optimal. What if you want to ensure that user should not create a Employee instance without providing name.
Sure different developers will have different methods to ensure that and have good design. I like to use builder for that. See following -
import static org.apache.commons.lang3.Validate.*; public class Employee { private String name; private int age; private Employee() {} public String getName() { return name; } public int getAge() { return age; } public static class EmployeeBuilder { private final Employee employee; public EmployeeBuilder() { employee = new Employee(); } public EmployeeBuilder setName(String name) { employee.name = name; return this; } public EmployeeBuilder setAge(int age) { employee.age = age; return this; } public Employee build() { validateFields(); return employee; } private void validateFields() { notNull(employee.name, "Employee Name cannot be Empty"); isTrue(employee.age >= 21, "Employee age cannot be less than 21"); } } }
Notice following points -
- We made constructor of Employee class private. So no one can directly instantiate Employee class. He or she has to use our Builder class.
- There is not setter methods in Employee class. Again builder should be used.
- Builder's build() methods validates our requirements like name cannot be null or age has to be more than 21.
- You can easily create Employee objects using -