Why composition is favorable over inheritance?

Author pic

Written By - Garvit Maloo

2 October, 2024

Composition and inheritance are two of the most popular principles of Object-oriented programming. While inheritance is super essential in making good softwares, there are some scenarios where we should avoid inheritance and prefer "composition before inheritance". In this blog, I have tried to explain such scenarios where composition can help us write better code.

Establishing the problem

One of the key characteristics of inheritance is that the child classes acquire all the properties and methods declared in the parent class. Sometimes, some of those properties or methods of the parent class might not be needed by the child class. Because of this, people often say inheritance make softwares "rigid".

Consider the code given below -

class ProgrammingLanguage{
    private String name;

    // constructor
    public ProgrammingLanguage(String name){
        this.name = name;
    }

    // methods
    public void execute(){
        System.out.println("Executing..");
    }
    public void compile(){
        System.out.println("Compiling..");
    }

    // getters
    public String getName(){
        return name;
    }
}

class Java extends ProgrammingLanguage {
    public Java(){
        super("Java");
    }

    // methods
    public void typeCheck(){
        System.out.println("Checking..");
    }
}

class Python extends ProgrammingLanguage{
    public Python(){
        super("Python");
    }

    // methods
    public void listFeatures(){
        System.out.println("Some amazing technical features: ");
    }
}

Pause for a second, and try to identify the problem in this code. Hint: Python is not a compiled language.

If you could not identify the problem, consider this code -

public static void main(String[] args) {
    Java javaProgram = new Java();
    javaProgram.compile();

    Python pythonProgram = new Python();
    pythonProgram.compile(); // here's the problem
}

Because python is not a compiled language, but extending Python class from ProgrammingLanguage class has made it rigid and the compile method from ProgrammingLanguage class has made it to the Python class as well. Clearly, this is redundant.

Solving the problem

To avoid this and make softwares "flexible", we can make use of composition. Composition is when we add objects of other classes in a class and delegate some of the functionality to the methods of that other class. So, this allows us to bring-in only those methods which are needed in our class.

Let's modify the above code to include composition and see how well it solves our problem.

First of all, we strip-off the compile method from the ProgrammingLanguage class and make two separate classes called Compiler and Interpreter as shown below.

abstract class ProgrammingLanguage {
    private String name;

    // constructor
    public ProgrammingLanguage(String name){
        this.name = name;
    }

    // methods
    abstract public void execute();

    // getters
    public String getName(){
        return name;
    }
}

class Compiler {
    private String name;

    // constructor
    public Compiler(String name){
        this.name = name;
    }

    // methods
    public void compile(){
        System.out.println("Compiling using " + name);
    }
}

class Interpreter {
    private String name;

    public Interpreter(String name){
        this.name = name;
    }

    // methods
    public void interpret(){
        System.out.println("Interpreting using " + name);
    }
}

Now let's make use of Composition and inheritance together and see what comes out.

class Java extends ProgrammingLanguage {
    // composition
    private Compiler compiler;

    public Java(){
        super("Java");
        this.compiler = new Compiler("javac");
    }

    // methods
    @Override
    public void execute(){
        compiler.compile();
        System.out.println("Executing..");
    }

    public void frameworks(){
        System.out.println("Spring and spring boot");
    }
}

class Python extends ProgrammingLanguage {
    // Composition
    private Interpreter interpreter;

    public Python(){
        super("Python");
        this.interpreter = new Interpreter("CPython");
    }

    // methods
    @Override
    public void execute(){
        interpreter.interpret();
        System.out.println("Executing..");
    }

    public void applications(){
        System.out.println("AI and ML");
    }
}

If we try to make objects of Java and Python classes in the main program, it would look something like this

public static void main(String[] args) {
    Java javaProgram = new Java();
    javaProgram.execute();
    // prints this
    // Compiling using javac
    // Executing..



    Python pyProgram = new Python();
    pyProgram.execute();
    // prints this
    // Interpreting using CPython
    // Executing..
}

As you can see, we do not have have any redundant methods in any of the two classes. Also, even if we make a new programming language which is just a compiled language, or just an interpreted language or even a compiled + interpreted language, we have the flexibility to do whatever it asks us to do. This is the power of composition.

So, as I described composition above, we are composing a class with objects of other classes and delegating some of the functionality to these other classes which is making our software flexible. We define composition as "has-a" relationship. For example, Python "has-a" interpreter, Java "has-a" compiler. Whereas we define inheritance as "is-a" relationship. Python "is-a" programming language and similarly Java "is-a" programming language.

So clearly, there is no right answer for which is important - inheritance or composition. Both are useful in making good applications, it's just we need to know when to use what and how.

That's all for this blog. Right now, I am learning some advanced concepts in object-oriented design and I am planning to make a complete blog series on Object-oriented design. So, stay tuned for more great content! Till then, keep learning and keep having fun!

Liked the content? Share it with your friends!
Share on LinkedIn
Share on WhatsApp
Share on Telegram

Related Posts

See All