Using Composition in Object Modeling
Using composition over inheritance is a common design pattern that is often discussed in terms of designing business logic components. However, composition can solve a number of problems in domain object modeling that are created by relying on inheritance to share interface or functionality. Composition is used to delegate implementation in logical units by enlisting the help of a reference to an object that implements the required functionality instead of inheriting from it. This reference can be changed to different implementations depending on the needs at the time making for a more flexible design. This same design can be used in domain modeling to overcome some of the problems caused by inheritance. The typical flawed example of using inheritance in object modeling is the Person
class which is often subclassed into Employee
, User
, Customer
and Vendor
classes.
public class Person { private Long id; private String firstName; private String lastName; }
public class User extends Person { private String userName; private String password; }
public class Customer extends Person { private int creditLimit; }
public class Employee extends Person { private Date beginDate; private Date endDate; }
The classic problems here are that a customer may not always be a person, and a person may be both a customer and a vendor and possibly even be an employee and user. It also ignites the age old debate of whether a user must be an employee or all employees are also users. Using inheritance severely restricts us by taking away our one chance to inherit from a single class and limits our future plans since we are claiming some hard rules such as “every user is a person”.
Also, how do we handle a person that leaves the company and comes back? Do we have two different instances of this person in the system? For that matter, most Person instances will be duplicated if a person is a customer and a user and employee. This also leads to problems with things like name changes where one version gets updated but the other doesn’t.
Role your own
One common solution to this problem is to let the Person
entity have a collection of roles that they perform so they can be either a user,an employee, a customer or vendor at the same time. This does solve the problems of not having to worry about inheritance hierarchies and whether a user is an employee or vice versa and also lets a person play all those roles at once. However, it only works where you know that every user, customer, vendor or employee will be a person.
It’s also not very clear how we should reference these entities. If I have an order and need to reference a customer, do I reference the person or the persons customer role?
Compose Yourself
Alternatively you can use composition to model the relationship instead of inheritance. We can define a Person
class which models just a person and we model Customers and Employees as separate classes that encapsulate the information specific to that role and has a reference to the person associated with it.The benefit here is that the different classes do not have to be subclassed from the Person
class which lets us inherit from whatever we want.
public class User { private Long id; private String userName; private String password; private Person person; }
and
public class Customer { private Long id; private Person person; private int creditLimit; }
public class Employee { private Long id; private Person person; private Date beginDate; private Date endDate; }
We don’t have to worry about whether Employee
should inherit from a User
or vice versa since they are entirely different entities but they reference the same person instance. We still have the ability to check for things like users that are employees by finding users and employees that have a matching person reference. Modeling for each of the entities is kept specific to what the entity represents. A customer contains customer information and an employee contains employee information with only a single reference to the person it is representing.
This solves our original problems, but also gives us far more in return. It is a far more flexible design because we can expand the definitions of the classes beyond the hard restriction that every customer must be a person. Let’s consider a situation where the developers have been asked to allow businesses to also be defined as customers in the system. With the Customer subclassing from Person design it would be a nightmare to change the code to accommodate this. On the other hand, if we designed Customer to use composition, we can easily resolve the problem.
public abstract class AbstractCustomer { private Long id; private int creditLimit; public abstract String getName(); }
We can create a person based customer by subclassing it and adding a reference to the person.
public class PersonCustomer extends AbstractCustomer { private Person person; public abstract String getName() { return person.getName(); } }
For a company based customer, we can again subclass it, this time adding a Company
reference.
public class CompanyCustomer extends AbstractCustomer { private Company company; public abstract String getName() { return company.getName(); } }
Notice that in the abstract base class, we define the getName()
method to return the name of the customer. This is made abstract and must be implemented in subclasses specific to the type of customer we are implementing. As a general rule, like any inheritance, common attributes that apply to all types of the entity should go in the base class because then you will be able to call those methods on references to the entity as the base class type instead of having to use a more specific subclass type. For example
String msg = "Hi There "+customer.getName();
is much cleaner than
String msg = "Hi There "; if (customer instanceof PersonCustomer) { msg = msg+((PersonCustomer)customer).getPerson.getPersonName(); } if (customer instanceof CompanyCustomer) { msg = msg+((CompanyCustomer)customer).getCompany.getCompanyName(); }
Another nice effect of this is modularity since you can add new customer types and the old queries will work the same way. For example, if you start with PersonCustomer
you might have a query which lists the customers with a certain credit limit.
select c from Customer c where c.creditLimit > 500
If you suddenly add the CompanyCustomer
class to the application, the query will still work and will now include company based customers in the list. Remember, the more attributes defined on the base class, the more attributes you will be able access or query on when obtaining instances as the base class. However, note that code driven attributes such as the getName()
example above cannot be used for querying in JPA since the attribute does not resolve to a database field. To query by person name you would have to drop down to using the actual class name to query the name fields in the person reference.
Unique People, non-unique references
If an employee leaves the company and comes back, we can deal with this much easier because we can set the end date for the existing Employee
instance when they leave and when they come back, we can create a new employee instance that references the same Person
entity. We still only have one Person
instance that is referenced by both Employee
instances and thus there is no duplication. This kind of modeling is not only more accurate in that one person was an employee on two different occasions, but it also provides a form of auditing trail for that person. If you store the Employee
reference, you can still use either the person or the employee id to search for data. If you wanted to see what orders this specific employee had filled in, you can search using either the employee id which would get the orders for this version of the employee only, or you could match based on the person id and see what orders that person had ever filled in as either employee instance. This also reflects the real world better since there is only ever one person, but they could have multiple periods as an employee.
Modeling with JPA
It is fairly easy to model the basic composition in JPA by adding a @ManyToOne
annotation to the entity reference (in this case the person).
If you plan on creating subclasses that are backed by different entities (i.e. PersonCustomer
and CompanyCustomer
) then you have to choose between different inheritance strategies. If there only a few differences between subclasses, you can probably just use a single table. If there are bigger differences between the subclasses, you might want to use a joined table inheritance strategy while keeping the core attributes of the parent class in the main table.
For legacy databases, you could be faced with the problem that the referenced entity id, such as the company or person id in the customer subclass, is stored in the same field in the database record. You can use the same field name for the reference in the subclasses and JPA will work with it.
public class PersonCustomer extends AbstractCustomer { @ManyToOne @JoinColumn(name="REF_ID") private Person person; } public class CompanyCustomer extends AbstractCustomer { @ManyToOne @JoinColumn(name="REF_ID") private Company company; }
Obviously, this method eliminates the opportunity to use foreign keys since a foreign key cannot reference two different tables. New designs should not use this method for this reason even though JPA allows it. If you let JPA drop and create the tables for you, with Hibernate at least, you will end up with a foreign key on the REF_ID
field to either the person or company table that will be incorrect when you assign the other subclass type to it. Through luck, it may not raise an error the first time around (if you happen to have an instance of one entity that has the same Id as the other entity type) but it will at some point.
If you really need this design, then you can just build the tables yourself without the foreign key, but you should use a separate field even though it means that each record will have blank field in it. With a separate or join table inheritance strategy, you have no choice but to use separate fields for the references.
A technique with many uses
This technique has many applications, but I haven’t really seen it discussed that much. Obviously, most published application examples (i.e. Pet store or my own Issue Tracker) are trivial or incomplete so they don’t really require this level of attention to modeling detail. However you start to find all sorts of places this technique can be used for larger domain models either to provide functionality needed now or a flexible design to meet future requirements. Obviously, we don’t want to use this for everything in case we want to extend any object in our model, but refactoring models once you have gone live can be problematic as it not only involves code refactoring, but database refactoring as well. Adopting this technique early (i.e. pre production) in places it is likely to be needed can save a lot of headaches later on.
As another example, let’s say you wanted to define a Location
which is basically an address and a few other attributes. These locations can be used for all sorts of things in various places in the application. We already have an Address
class used throughout our code and we can embed it in our Location
.
public class Location { @Embedded private Address address; private Date createdOn; private String description; }
Half way through your project you find that since this location class is used everywhere, the needs have changed somewhat and in some places need to have a dynamic address that belongs to a person and needs to be updated when the persons address changes, and some places need a static fixed address that never changes (i.e. delivery address an order was sent to which never changes).
We can easily implement this by creating a Location
class that is subclassed into the different implementations that source the address from different places.
public abstract class Location { public Address getAddress(); private String description; private Date createdOn; } public class FixedLocation extends Location { @Embedded private Address address; public Address getAddress() { return address; } } public class PersonLocation extends Location { @ManyToOne private Person person; public Address getAddress() { return person.getPrimaryAddress(); } }
Not only have we provided a solution for our fixed and dynamic address problem, since FixedLocation
will store the address internally while the others will dynamically obtain the latest address from the Person
, but we have provided a structure that will allow us to turn any entity into an address provider for our Location
class. In some ways, we are using it as more of an adapter pattern since the address sources no longer have a common type. We are using an adapter pattern to make the different implementation sources fit with our Location
interface.
I have found a number of uses for such compositions/adapters in places where inheritance would have severely limited my options or provided some difficult challenges, and by implementing this concept I’ve been able to make my models far more flexible and be able to accommodate new needs quickly.
4 thoughts on “Using Composition in Object Modeling”
Comments are closed.
Very nice article, and you couldn’t be more right about the approach. In any non-trivial application this is fundamental to get right the first time.
Keep the great posts coming!