Class inheritance 2026-10-24
Class inheritance defines a software class using another software class's implementation and may add or override behavior. When it also establishes subtyping, the derived software class must satisfy the base software class's behavioral contract for clients to substitute it safely. Code reuse alone is not sufficient justification for an inheritance relationship. Object composition often better represents a software object containing a replaceable component.
A subclass may depend on its superclass's internal calls to overridable object methods. A base implementation change that preserves the base abstraction can then change subclass behavior. For example, a set subclass may count insertion attempts by overriding insertion. If a revised deletion operation rebuilds the set by dynamically dispatched clear and insert calls, it unexpectedly resets and increases that counter. Object composition can isolate the counting wrapper from the component's internal dispatch, provided only wrapper operations can mutate the owned component.
Use object composition: a logging wrapper owns a private ResizableFrame and delegates dimension operations to it, rather than being its subclass. The wrapper takes the before/after area around each public operation. Its own flip calls the component's whole flip once, compares only the completed states, and therefore records no increase. The component must be exclusively owned or defensively copied, so clients cannot bypass the wrapper's accounting.
The component's internal calls cannot dynamically dispatch into the separate wrapper. This prevents the setter-based flip implementation change from creating false log entries and reduces dependence on undocumented superclass internals. The wrapper can also choose a smaller interface. Costs include forwarding code and the loss of automatic ResizableFrame subtyping: code requiring that concrete superclass cannot necessarily accept the wrapper. A shared abstract frame interface can recover substitutability while keeping implementation reuse based on composition. Composition improves this dependency boundary; it does not remove the need for a clear mutation and ownership contract.
A suitable relationship is a circle implementing a read-only shape object interface whose operations include area. Every circle satisfies that object interface's behavioral contract, so clients using a shape can use a circle through subtype polymorphism; dynamic dispatch selects its area implementation.
A car is not appropriately modeled as a subclass of an engine merely because it needs engine behavior. Use object composition: the car contains an engine and delegates engine-related work to it. An engine is a component of a car, not a category containing cars. The design can replace the component independently without making all engine operations part of the car's public object interface. Class inheritance should reflect substitutability, rather than serve solely as a convenient way to obtain another software class's code.
Give the wrapper exclusive ownership of a fresh delegate. It maintains its own inherited size so that inherited emptiness queries remain correct:
class IntegerSetWithCount extends IntegerSetPattern() {
  private val delegate = new IntegerSet()
  private var attempts = BigInt(0)

  override def contains(x: Int): Boolean = delegate.contains(x)

  override def insert(x: Int): Unit = {
    attempts += 1
    val wasPresent = delegate.contains(x)
    delegate.insert(x)
    if (!wasPresent) size += 1
  }
  override def delete(x: Int): Unit = {
    val wasPresent = delegate.contains(x)
    delegate.delete(x)
    if (wasPresent) size -= 1
  }
  override def pop(): Int = {
    if (isEmpty) throw new NoSuchElementException("set is empty")
    val answer = delegate.pop()
    size -= 1
    answer
  }
  override def clear: Unit = {
    delegate.clear
    size = 0
    attempts = BigInt(0)
  }
  def getCount: BigInt = attempts
}
Use the explicit membership signature and fresh default-constructible concrete set qualified in part d. The representation invariant is that the wrapper's size equals the delegate's cardinality and its counter equals client insertion attempts since construction or the latest successful client clear. The updates shown preserve that invariant when delegated operations fulfill their set contracts and failure checks leave the delegate unchanged. A duplicate does not increase size; deleting an absent element does not decrease it. Size changes follow successful delegation, whereas an attempted insertion is counted immediately.
Object composition prevents internal delegate calls from dispatching into the wrapper's counter object methods. It therefore survives the particular base revision in part e. The cost is forwarding code, another software object, some redundant size state imposed by the chosen abstract superclass, and extra membership queries. Future object interface additions must also be forwarded explicitly. This solution is an alternative definition of the software class in part d, not a second simultaneous definition.
Class inheritance offers a compact specialized implementation: the scaled sum is also a SumChannel and can be used where that concrete type is required, while its inherited state handling is reused directly.
The decorator supports run-time object composition over any Channel implementation, including an already constructed stateful software object, without editing or subclassing each concrete software class. Its extra software object and delegation are a small cost. The two approaches therefore serve different reuse needs; the wrapper need not pretend to be the concrete sum-channel software class.