JavaRush /Java Blog /Random EN /Inheritance vs Composition in Java
dio
Level 16
Москва

Inheritance vs Composition in Java

Published in the Random EN group
This article illustrates the concepts of inheritance and composition in Java. The first example demonstrates inheritance and then shows how to improve inheritance design using composition. We will summarize how to choose between them at the end. Inheritance vs Composition in Java - 1

1. Inheritance

Let's assume that we have a class Insect(English insect) This class contains two methods: 1. move()(from English move) and 2. attack()(from English attack)
class Insect {
	private int size;
	private String color;

	public Insect(int size, String color) {
		this.size = size;
		this.color = color;
	}

	public int getSize() {
		return size;
	}

	public void setSize(int size) {
		this.size = size;
	}

	public String getColor() {
		return color;
	}

	public void setColor(String color) {
		this.color = color;
	}

	public void move() {
		System.out.println("Move");
	}

	public void attack() {
		move(); //assuming an insect needs to move before attacking
		System.out.println("Attack");
	}
}
Now you want to define a class Bee(English bee), which is one of the types Insect, but has different implementations attack()of and move(). This can be done using inheritance:
class Bee extends Insect {
	public Bee(int size, String color) {
		super(size, color);
	}

	public void move() {
		System.out.println("Fly");
	}

	public void attack() {
		move();
		super.attack();
	}
}
public class InheritanceVSComposition {
	public static void main(String[] args) {
		Insect i = new Bee(1, "red");
		i.attack();
	}
}
The class hierarchy diagram is quite simple: Inheritance vs Composition in Java - 2Execution result:
Fly
Fly
Attack
"Fly" is typed twice, so the method move()is called twice. But it should only be called once. The problem is caused by the super.attack(). The method attack ()calls a move()class method Insect. When a subclass calls super.attack (), it also calls the overridden method move(). To fix the problem we can:
  1. Eliminate the attack()subclass method. This will make the subclass dependent on the attack()superclass's method implementation. If attack()attack()the superclass starts using a different method for moving, the subclass will need to change too. This is bad encapsulation.
  2. Rewrite the method attack()as follows:

    public void attack() {
    	move();
    	System.out.println("Attack");
    }
  3. This guarantees the correct result because the subclass no longer depends on the superclass. However, the code is a duplicate of the superclass. (the method attack()does more complex things than just outputting a string). This is not good software design and there should be no duplicate code.

This inheritance design is bad because the subclass depends on the implementation details of its superclass. If the superclass changes, the subclass will not work correctly.

2. Composition

You can use composition instead of inheritance. Let's look at a solution using it. The function attack()is abstracted as an interface.
interface Attack {
	public void move();
	public void attack();
}
Different types of attack can be defined by implementing the Attack interface.
class AttackImpl implements Attack {
	private String move;
	private String attack;

	public AttackImpl(String move, String attack) {
		this.move = move;
		this.attack = attack;
	}

	@Override
	public void move() {
		System.out.println(move);
	}

	@Override
	public void attack() {
		move();
		System.out.println(attack);
	}
}
Since the attack function is external, the class Insectno longer contains it.
class Insect {
	private int size;
	private String color;

	public Insect(int size, String color) {
		this.size = size;
		this.color = color;
	}

	public int getSize() {
		return size;
	}

	public void setSize(int size) {
		this.size = size;
	}

	public String getColor() {
		return color;
	}

	public void setColor(String color) {
		this.color = color;
	}
}
Class Bee(from English Bee), how a type Insectcan attack.
// This wrapper class wrap an Attack object
class Bee extends Insect implements Attack {
	private Attack attack;

	public Bee(int size, String color, Attack attack) {
		super(size, color);
		this.attack = attack;
	}

	public void move() {
		attack.move();
	}

	public void attack() {
		attack.attack();
	}
}
Class diagram: Inheritance vs Composition in Java - 3
public class InheritanceVSComposition2 {
	public static void main(String[] args) {
		Bee a = new Bee(1, "black", new AttackImpl("fly", "move"));
		a.attack();

		// if you need another implementation of move()
		// there is no need to change Insect, we can quickly use new method to attack

		Bee b = new Bee(1, "black", new AttackImpl("fly", "sting"));
		b.attack();
	}
}
Execution result:
fly
move
fly
sting

3. When to use these approaches?

The following 2 points can help you decide between inheritance and composition:
  1. If you are dealing with a relationship between classes of the form "IS" and a class wants to provide all its interfaces to another class, then inheritance is preferable.
  2. if the relationship is "HAS", then composition is preferred.
Thus, inheritance and composition have their own applications and it is worth understanding their merits. Links:
  1. Bloch, Joshua. Effective java. Pearson Education India, 2008
  2. https://stackoverflow.com/questions/49002/prefer-composition-over-inheritance
  3. https://www.javaworld.com/article/2076814/core-java/inheritance-versus-composition--which-one-should...
Link to original article Translated
Comments (1)
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION
16 August 2023
Thanks a lot for sharing it here with us. Well, I have seen little example for both inheritance and composition. Inheritance:

class Superclass {
    // Superclass members
}

class Subclass extends Superclass {
    // Subclass members
}
Composition:

class Engine {
    // Engine members
}

class Car {
    private Engine carEngine;

    // Car methods using carEngine
}
Thanks