A logbook for curious waters
← Software Engineering Field Guide

Replace Subclass with Fields

Before you begin: Inheritance, overriding, constructors, and fields.

Replace Subclass with Fields replaces subclasses that differ only in constant data with one class storing that data. Instead of creating a class for every capacity, color, or label, pass the value to an instance. Fowler’s newer catalog calls this Remove Subclass. 1

Original version: a class for every chest size

public abstract class Chest
{
    public abstract int Capacity();
}
public sealed class SmallChest : Chest
{
    public override int Capacity() { return 10; }
}
public sealed class LargeChest : Chest
{
    public override int Capacity() { return 30; }
}
class Chest:
    def capacity(self):
        raise NotImplementedError


class SmallChest(Chest):
    def capacity(self):
        return 10


class LargeChest(Chest):
    def capacity(self):
        return 30

The subclasses do not implement different storage algorithms. Each supplies one fixed number. Adding a twenty-slot chest would require another class for another number.

Refactored version: capacity is data

public sealed class Chest
{
    private readonly int capacity;
    public Chest(int capacity) { this.capacity = capacity; }
    public int Capacity() { return capacity; }
}
Chest small = new Chest(10);
Chest large = new Chest(30);
Console.WriteLine(small.Capacity()); // 10
Console.WriteLine(large.Capacity()); // 30

The behavior is one getter; only its stored value differs. An additional size no longer requires a subclass declaration.

class Chest:
    def __init__(self, capacity):
        self._capacity = capacity

    def capacity(self):
        return self._capacity
small = Chest(10)
large = Chest(30)
print(small.capacity())  # 10
print(large.capacity())  # 30

Each instance stores its own capacity. The object type no longer encodes the size.

Preserve distinctions that matter

Before deleting subclasses, inspect type checks, serialization tags, public constructors, and overrides. A class name can be part of a contract even if its methods look trivial. Callers using SmallChest as an identity or dispatch signal need migration.

The new constructor also permits capacities beyond the two original choices. A factory or validation can restrict that when necessary. This example deliberately demonstrates a more general data representation; it is not an assertion that every integer is a valid production chest size.

Keep subclasses when their distinct behavior or substitutable domain roles are meaningful. Remove them when they merely turn a table of data into a collection of files.

Sources

  1. Martin Fowler — Remove Subclass.