Menu
CoddyTech

Composition vs Inheritance

Part of the Object Oriented Programming section of Coddy's GDScript journey. Lesson 40 of 65.

Classes reuse code in two ways. Inheritance (extends) says a class is a kind of another class: a Mage is a Player. Composition says a class has a part: it stores another object in a member variable and hands work over to it:

# motor.gd
class_name Motor
extends RefCounted

func start() -> String:
	return "motor running"

# car.gd
class_name Car
extends RefCounted

var motor := Motor.new()

func start() -> String:
	return "Car starts, " + motor.start()

Car.new().start() returns Car starts, motor running. The car does not know how a motor works: it calls motor.start(). Handing a call to a part like this is called delegation. (The class is named Motor because Engine is already a Godot class.)

Composition pays off when parts change or combine. With inheritance, a walking character and a flying character are two subclasses, and a bat that starts to fly cannot change its class. With composition, the character has a movement part, and switching it is one assignment:

# character.gd
class_name Character
extends RefCounted

var char_name: String
var movement: Movement

func _init(new_name: String, new_movement: Movement) -> void:
	char_name = new_name
	movement = new_movement

func move() -> String:
	return char_name + " " + movement.move()

# main.gd
var bat := Character.new("Bat", Walk.new())
print(bat.move()) # Bat walks
bat.movement = Fly.new()
print(bat.move()) # Bat flies

Here Walk and Fly extend a small Movement class: the two techniques work together. A GDScript class can extend only one class, so composition is also how one object gets several abilities, by holding several parts.

Use inheritance for a real "is a" relationship that never changes. Use composition when the class "has" something, or when the part must be swapped while the game runs.

challenge icon

Challenge

Easy

A knight does not inherit from a weapon: a knight has a weapon, and can switch it. Build both classes.

  • Weapon stores weapon_name and damage, both set by the constructor: Weapon.new("Sword", 7).
  • Knight stores knight_name (from the constructor) and a weapon, which is null at the start.
  • equip(new_weapon) replaces the weapon and drop() sets it back to null.
  • attack() returns Ada attacks with Sword for 7 damage, using the knight's name and the weapon's name and damage, or Ada has no weapon when there is none.

The supplied code reads the knight's name and how many commands follow (equip Sword 7, drop, attack) and runs them. For Ada and attack, equip Sword 7, attack, equip Axe 12, attack, drop, attack it prints:

Ada has no weapon
Equipped Sword
Ada attacks with Sword for 7 damage
Equipped Axe
Ada attacks with Axe for 12 damage
Dropped the weapon
Ada has no weapon

Your code goes in weapon.gd and knight.gd. main.gd holds the supplied input/output code and cannot be edited.

Try it yourself

extends Node

func _ready():
	# Supplied input/output code: keep it as it is
	var knight := Knight.new(OS.read_string_from_stdin())
	var count := int(OS.read_string_from_stdin())
	for i in count:
		var parts := OS.read_string_from_stdin().split(" ")
		if parts[0] == "equip":
			knight.equip(Weapon.new(parts[1], int(parts[2])))
			print("Equipped ", parts[1])
		elif parts[0] == "drop":
			knight.drop()
			print("Dropped the weapon")
		else:
			print(knight.attack())
quiz iconTest yourself

This lesson includes a short quiz. Start the lesson to answer it and track your progress.

All lessons in Object Oriented Programming

Practice on your own: Online GDScript compiler