Menu
CoddyTech

Factory Pattern

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

The Factory pattern moves the choice of which class to create into one place. The rest of the program asks the factory for "a goblin" and gets an object back, without naming the class Goblin.

The products share a parent class, so the code that uses them works with any of them:

# enemy.gd
class_name Enemy
extends RefCounted

var hp := 10

func attack() -> String:
	return "The enemy waits"
# goblin.gd
class_name Goblin
extends Enemy

func attack() -> String:
	return "The goblin stabs"
# orc.gd
class_name Orc
extends Enemy

func attack() -> String:
	return "The orc smashes"

The factory is a class with a static func that turns a name into an object. Its return type is the parent class, so it can return any kind of enemy, or null for a name it does not know:

# enemy_factory.gd
class_name EnemyFactory
extends RefCounted

static func create(kind: String) -> Enemy:
	match kind:
		"goblin":
			return Goblin.new()
		"orc":
			return Orc.new()
	return null
# main.gd
for kind in ["orc", "goblin", "slime"]:
	var enemy := EnemyFactory.create(kind)
	if enemy == null:
		print("Unknown enemy: ", kind)
	else:
		print(enemy.attack())
The orc smashes
The goblin stabs
Unknown enemy: slime

Names like these usually come from data: a spawn table, a level file, a command the player typed. The factory is the only place that maps them to classes, so adding a Dragon takes one new file and one new branch in the match.

A factory can also set the object up before returning it, so every caller gets an enemy that is ready to use:

static func create_boss(kind: String, level: int) -> Enemy:
	var boss := create(kind)
	if boss != null:
		boss.hp = 100 * level
	return boss

EnemyFactory.create_boss("orc", 3) returns an Orc with 300 HP.

challenge icon

Challenge

Medium

A spawn table names enemies and levels. Enemy (enemy.gd) and its subclass Goblin (goblin.gd) are written; finish the rest:

  • In orc.gd, the constructor _init(level) passes the name Orc, 25 * level HP and 4 + level damage to the parent constructor, the way Goblin does.
  • In enemy_factory.gd, static func create(kind, level) returns a Goblin for goblin and an Orc for orc, created with that level. Upper-case letters in the name do not matter (ORC is an orc). Any other name returns null.

The supplied code reads how many spawns follow, then one KIND LEVEL line per spawn, and prints each enemy or Unknown enemy: KIND, then how many enemies were spawned. For goblin 2, Orc 3 and slime 1 the output is:

Goblin: 20 HP, 4 damage
Orc: 75 HP, 7 damage
Unknown enemy: slime
Spawned: 2

Your code goes in enemy_factory.gd and orc.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 spawned := 0
	var count := int(OS.read_string_from_stdin())
	for i in count:
		var parts := OS.read_string_from_stdin().split(" ")
		var enemy := EnemyFactory.create(parts[0], int(parts[1]))
		if enemy == null:
			print("Unknown enemy: ", parts[0])
		else:
			spawned += 1
			print(enemy.describe())
	print("Spawned: ", spawned)
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