A logbook for curious waters
← Software Engineering Field Guide

Replace Delegation with Inheritance

Before you begin: Composition, inheritance, and substitutability.

Replace Delegation with Inheritance makes a forwarding class inherit the behavior it previously obtained from a contained object. It can remove repetitive wrappers, but it is appropriate only when the new subtype genuinely supports the base class’s entire public contract. 1

Original version: a printable document forwards every document property

The contained document is created privately for this wrapper; it is not shared with another owner.

public class Document
{
    public string Name { get; }
    public int LineCount { get; }
    public Document(string name, int lineCount) { Name = name; LineCount = lineCount; }
}
public sealed class PrintableDocument
{
    private readonly Document document;
    public PrintableDocument(string name, int lineCount)
    {
        document = new Document(name, lineCount);
    }
    public string Name { get { return document.Name; } }
    public int LineCount { get { return document.LineCount; } }
    public string Summary() { return Name + ": " + LineCount + " lines"; }
}
class Document:
    def __init__(self, name, line_count):
        self.name = name
        self.line_count = line_count


class PrintableDocument:
    def __init__(self, name, line_count):
        self._document = Document(name, line_count)

    @property
    def name(self):
        return self._document.name

    @property
    def line_count(self):
        return self._document.line_count

    def summary(self):
        return self.name + ": " + str(self.line_count) + " lines"

The wrapper supports all shown document data and adds a summary. Its forwarding properties contain no independent rule.

Revised version: inherit the document contract

public class Document
{
    public string Name { get; }
    public int LineCount { get; }
    public Document(string name, int lineCount) { Name = name; LineCount = lineCount; }
}
public sealed class PrintableDocument : Document
{
    public PrintableDocument(string name, int lineCount) : base(name, lineCount) { }
    public string Summary() { return Name + ": " + LineCount + " lines"; }
}

The same public reads and summary remain, and the object is now usable as a Document.

class Document:
    def __init__(self, name, line_count):
        self.name = name
        self.line_count = line_count


class PrintableDocument(Document):
    def summary(self):
        return self.name + ": " + str(self.line_count) + " lines"

The subclass inherits initialization and attributes. Unlike the old wrapper’s getter-only properties, those attributes are now ordinarily writable. That access difference is real and must be accepted or explicitly constrained in a production migration.

Fewer forwarding methods are not enough reason

Inheritance exposes present and future base-class operations and couples the subtype to base implementation decisions. It also prevents independently replacing the contained implementation as ordinary composition allows.

Do not apply this move when the delegate is shared, has a separate identity or lifecycle, or supports operations the wrapper intentionally hides. Those are strong reasons to retain composition.

The examples preserve the shown reads and summary, but do not claim identical reflection, type identity, mutation access, or serialization behavior. Inheritance is justified by substitutability, not by the convenience of deleting a few wrapper methods.

Sources

  1. Martin Fowler — Replace Delegation with Inheritance.