When we were discussing inheritance and interfaces we discussed the is-a relationship that is created. For example, if class A extends from class B, we say that A is-a type of B.
Another important relationship is the has-a, when one class uses one or more objects of a different type in its fields.
To understand this let’s take a look at an example and assume we’re building a system for a retailer where users should have an address registered in the system. We could think about it like this:
public class User {
private String username;
private String address;
private String city;
private String country;
// constructors and other methods omitted
public void printUserAddress() {
System.out.println("Address for user '" + username + "' is: ");
System.out.println(address + ", " + city + ", " + country);
}
}
However, there is state in this class that doesn’t seem to belong here. All the address fields might make more sense in a class responsible of holding the Address
data, like this:
public class Address {
private String address;
private String city;
private String country;
public Address(String address, String city, String country) {
this.address = address;
this.city = city;
this.country = country;
}
public void printAddress() {
System.out.println(address + ", " + city + ", " + country);
}
}
In this way we can change our User
class to move all the address responsibilities out of it and make use of the Address
class instead:
public class User {
private String username;
private Address address;
public User(String username, Address address) {
this.username = username;
this.address = address;
}
public void printUserAddress() {
System.out.println("Address for user '" + username + "' is: ");
address.printAddress();
}
}
This means that a User
has-an Address
and we established a composition relationship. Alternatively, we could’ve had our User
class extend from our Address
such that it would inherit all of the address state it requires, however let’s see why this isn’t the best idea in this case.
Composition over inheritance
When designing systems it is a good practice to favor composition over inheritance. This doesn’t mean that inheritance should be avoided, inheritance has its place in systems design when a clear is-a relationship exists.
In our example above, we used composition over inheritance as there is no clear is-a relationship between the two classes. This is, a User
isn’t a type of Address
.
Let’s assume we also need to manage the available physical stores the retailer has. Each store should have a short name that easily indicates where the shop is located (for example, "New York - Manhattan") and the full address to be displayed.
We could define a Store
class in the same way as we approached our User
class initially, but, we already have a class that is responsible of the Address
details and as much as possible we should reuse it:
public class Store {
private String storeName;
private Address storeAddress;
public Store(String storeName, Address storeAddress) {
this.storeName = storeName;
this.storeAddress = storeAddress;
}
public void printStoreAddress() {
System.out.println("Address for store '" + storeName + "' is: ");
storeAddress.printAddress();
}
}
Now, if we compare our User
and Store
classes in this example, both of them are very similar and it is tempting to abstract the commonalities of the two classes. This is, both have a name
and an address
, their only difference is the method to print the address details.
For example, we could have our Store
class extend from User
and override the printAddress
method, but would this be correct? A Store
isn’t really a type of User
, and although Java would let us define this relationship it might not be in our best interest.
In this case, we decided to use Composition
by abstracting away the commonalities of the two classes into an Address
class and adding a has-a relationship between the User
and the Address
class and also between the Store
and the Address
class.
One of the benefits is that by using composition we aren’t declaring type relationships between our classes that don’t really exist and the responsibilities of each class aren’t mixed together.
On the other hand, let’s assume we’re asked to add an Administrator
user that can login to our system and belongs to a Store
. In this particular case, our Administrator
is-a type of User
so inheritance might make more sense and we could define our Administrator
class as follows:
public class Administrator extends User {
private Store store;
public Administrator(String username, Address address, Store store) {
super(username, address);
this.store = store;
}
@Override
public void printUserAddress() {
System.out.println("Administrator of Store:");
store.printStoreAddress();
super.printUserAddress();
}
}
What would’ve happened in this case if we decided to have our Store class extend from our User class? What complications would have we faced when defining our Administrator class?
|
A note on composition and aggregation
Composition is a very important concept to bear in mind and is often mentioned in literature when talking about systems design. One of the most recognized books Effective Java discusses what we mentioned above of favoring Composition over Inheritance.
However, if you look for documentation around this, you’ll find 3 types of relationships: Association, Aggregation and Composition.
Composition is the strongest type and implies ownership of the life-time of the contained object. For example, in our User
case, if the Address
ceases to exist when the User
object is removed from the system then we’re talking about composition.
If there is no implication of ownership, for example, if we can reuse the same Address
object for a User
and for a Store
, then we’ll be talking about aggregation.
Composition is a strong type of has-a relationship, aggregation is a weak type of has-a relationship. The emphasis we’re making on this section is on the has-a relationship as a whole and not on the difference between aggregation and composition.
We won’t go into more details in this course as it is beyond the scope of what we’re covering here, but we encourage readers to understand and try the different types of relationships as these are important in systems design. These concepts will be covered in a separate course. |