Java OOP: Composition Tutorial

In this Java tutorial we learn how to design loosely coupled applications with composition and why you should favor it above inheritance.

We cover how to instantiate a class within another class and how to access class members through multiple objects.

Lastly, we take a look at the pro's and cons of inheritance and composition.

What is composition

Composition is a type of relationship between classes where one class contains another, instead of inheriting from another.

Composition should be favored above inheritance because it’s more flexible and allows us to design loosely coupled applications.

As an example, let’s consider an application that converts a video from one format to another. Typically, we would create a converter class that contains the details of the file and the functionality to convert it.

Then, we could have another class that inherits from the converter and sends a notification to the user when it’s done, pulling details like the name, size etc. from the parent converter class.

But what if we wanted to add audio file conversion to our application. We would have to write another notification class, this time inheriting from the audio converter.

This creates two problems:

  • The first problem is that we have two child classes that essentially do the same thing.
  • The second problem is that the children heavily depend on their respective parents. If we needed to change something in the parent converter classes, we could break the functionality of the children.

The solution is composition. Where inheritance can be thought of as an is-a relationship, composition can be thought of as a has-a relationship.

Instead of creating a child class that inherits functionality from a parent class, we create stand-alone classes that contain other classes.

Simply put, we create an object instance of a class inside another class.

Convert inheritance to composition in Java

We can convert any inheritance relationship to composition. To create this association between classes, we instantiate a new class object inside the constructor of the class we want to use it.

Syntax:
class class_1 {}

class class_2 {

    // var of type 'class_1'
    class_1 class_1_obj;

    // instantiate new object
    // into var 'object_name'
    class_2() {
        this.object_name = new class_1();
    }
}

It’s similar to how we construct properties, except this time we create a new instance object of another class.

Example:
public class Program {
    public static void main(String[] args) {}
}

class Pushable {

    public void sendPushMessage() {
        System.out.println("File converted successfully.");
    }
}

class Converter {

    // variable of type
    // 'Pushable'
    Pushable push;

    // instantiate new object
    // into 'push' variable
    Converter() {
        this.push = new Pushable();
    }

    public boolean convert() {
        System.out.println("Converting...");
        return true;
    }
}

The ‘Converter’ class now has access to the functionality to sendPushMessage() without inheriting from the ‘Pushable’ class.

Functionality classes like Pushable are often named with “able”, “can” or “has”. Like Flyable, CanDrive, HasFood etc.

How to access an object in a class in Java

To access an object in a class we go another level deeper with dot notation. Let’s see the syntax first, then break it down.

Syntax:
class class_1 {}

class class_2 {

    // var of type 'class_1'
    class_1 class_1_obj;

    // instantiate new object
    // into var 'object_name'
    class_2() {
        this.object_name = new class_1();
    }
}

var class_2_obj = new Class_2();

class_2_obj.class_1_obj.method();

Before we can access anything in ‘Class_2’, we have to create an object of it. Once we have the ‘class_2_obj’, we can access the ‘class_1_obj’ with dot notation.

Because ‘class_1_obj’ is an object itself, we have to use another level of dot notation to access its method.

An example will illustrate it better.

Example:
public class Program {
    public static void main(String[] args) {

        // class 2 object
        Converter con1 = new Converter();

        // access class 2 object member
        con1.convert();

        // access class 1 object member
        // through class 2 object
        con1.push.sendPushMessage();
    }
}

class Pushable {

    public void sendPushMessage() {
        System.out.println("File converted successfully.");
    }
}

class Converter {

    // class_1 object
    Pushable push;

    Converter() {
        this.push = new Pushable();
    }

    public boolean convert() {
        System.out.println("Converting...");
        return true;
    }
}

In the example above, we access the ‘push’ object that was created in the ‘Converter’ class constructor, from the ‘con1’ object. From there, we simply access the ‘sendPushMessage()’ through dot notation.

Why favor composition over inheritance

You might be asking, if composition only gives us indirect access, why should we use it?

Well, inheritance can be abused very easily. It could lead to a large hierarchy of classes that depend on each other, which is fragile.

If we change a class at the top of the hierarchy, any class that depend on it could be affected and may need to be changed as well.

As an example, let’s consider a class called ‘Animal’ and two child classes that inherit from it.

Example:
public class Program {
    public static void main(String[] args) {}
}

class Animal {

    public void eat() {
        System.out.println("Eating...");
    }

    public void walk() {
        System.out.println("Walking...");
    }
}

class Dog extends Animal {}
class Cat extends Animal {}

The ‘Dog’ and the ‘Cat’ can both eat() and walk() . But if we wanted to add a ‘Fish’, the hierarchy would need to be changed.

We would need something like a ‘Mammal’ class that inherits from ‘Animal’. Then, ‘Dog’ and ‘Cat’ can inherit from ‘Mammal’.

‘Fish’ will have to inherit from ‘Animal’, but also be separate from ‘Mammal’ and depending on what we want to do, ‘Animal’ may have to change.

Now let’s consider that instead of is-a animal, the classes now has-a animal.

Example:
public class Program {
    public static void main(String[] args) {}
}

class Animal {

    public void eat() {
        System.out.println("Eating...");
    }
}

class Walkable {

    public void walk() {
        System.out.println("Walking...");
    }
}

class Swimmable {

    public void swim() {
        System.out.println("Swimming...");
    }
}

class Dog {

    Animal animal;
    Walkable walkable;
    Swimmable swimmable;

    Dog() {
        this.animal = new Animal();
        this.walkable = new Walkable();
        this.swimmable = new Swimmable();
    }
}

class Fish {

    Animal animal;
    Swimmable swimmable;

    Fish() {
        this.animal = new Animal();
        this.swimmable = new Swimmable();
    }
}

This time, we add a class for any animal that can swim, and any animal that can walk.

The ‘Cat’ and ‘Dog’ classes can implement both ‘Walkable’ and’ Swimmable’. The ‘Fish’ class can implement Swimmable, skipping the others.

If we wanted to add a bird, we could add a ‘Flyable’ class, and the bird could implement ‘Walkable’, ‘Swimmable’ and ‘Flyable’.

The application is now loosely coupled, as each class stands on its own and is not dependent on another class. Let’s look at a full example.

Example:
public class Program {
    public static void main(String[] args) {

        System.out.println("Nemo the fish's activities:");
        Fish nemo = new Fish();
        nemo.swimmable.swim();

        System.out.println("");

        System.out.println("Tweety the bird's activities:");
        Bird tweety = new Bird();
        tweety.walkable.walk();
        tweety.swimmable.swim();
        tweety.flyable.fly();
    }
}

class Walkable {

    public void walk() {
        System.out.println("Walking...");
    }
}

class Swimmable {

    public void swim() {
        System.out.println("Swimming...");
    }
}

class Flyable {

    public void fly() {
        System.out.println("Flying...");
    }
}

class Bird {

    Walkable walkable;
    Swimmable swimmable;
    Flyable flyable;

    Bird() {
        this.walkable = new Walkable();
        this.swimmable = new Swimmable();
        this.flyable = new Flyable();
    }
}

Inheritance vs Composition: Pro's and Cons

We don’t mean that inheritance is a bad thing, a developer will still need to use inheritance from time to time.

Composition is just an alternative that we should consider, before using inheritance.

Let’s look some pro’s and cons of both inheritance and composition.

  • Inheritance Pro’s: Reusable code, easy to understand
  • Inheritance Cons: Tightly coupled, fragile, can be abused

  • Composition Pro’s: Reusable code, flexibility, loosely coupled

  • Composition Cons: Harder to understand

Summary: Points to remember

  • Composition is instantiating and accessing a class inside another instead of inheriting from it.
  • Any inheritance relationship can be converted into composition.
  • A class that’s instatiated inside another should use the this keyword to refer to the calling object.
  • Access to the inner object is done via dot notation, multiple levels deep.
  • We should typically try to favor composition over inheritance.