Chapter 5 - Java for Beginners Course

Class Composition

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.
Code in GitHub

Get the code for this tutorial using the links below.

Project Repo
Download code for this step
Main class for this step
Dependencies

This is a list of recommended tutorials or courses that might be useful before starting this one.

Contents
Welcome to the Course!
Course Introduction
Chapter 1 - Building Blocks
Quick introduction to Java Variables Classes And Objects Class Example - Defining a class Object Examples - Creating instances Java Application Example - Running our first app Accessing class members - The dot operator Packages - Organizing the code
Chapter 2 - Primitives and Operators
Primitives Arithmetic Operators Assignment Operator Unary Operators Equality and Relational Operators Conditional Operators
Chapter 3 - Statements and Control Flow
Expressions Statements If-Then Statement If-Then-Else Statement More If Statements Switch Statement While and Do-While Statements For Statement Branching Statements Exception Handling
Chapter 4 - Code Example
Example Project - A Simple Vending Machine Adding money Delivering Items Giving Change
Chapter 5 - Classes and Interfaces
Introduction Access Level Modifiers Class Declaration - Class, Methods and Fields Class Declaration - Constructors Inheritance Basics Inheritance - Constructors Inheritance - Methods and Fields Polymorphism Abstract Classes and Methods Interfaces Static Class Members Class Composition Final Classes and Class Members Generic Classes
Chapter 6 - Base Object Behaviors
Introduction Type Comparison Type Casting Object Equality - The Contract Object Equality - Common Pitfalls Object String Representation Garbage Collection Object Comparison Primitive Wrappers and Autoboxing
Chapter 7 - Data Structures
Introduction Arrays - Declaration and Creation Arrays - Basic Operations Core Collection Interfaces List and ArrayList - Basic Operations ArrayList Internals Introduction to Hash Tables Map and HashMap - Basic Operations Set and HashSet - Basic Operations
Chapter 8 - Anonymous classes and lambdas
Introduction Filtering a List Anonymous Classes Lambdas Built-in Functional Interfaces
Chapter 9 - Streams
Introduction Creating Streams Intermediate Operations Terminal Operations