Adapter pattern 2026-10-25
An adapter presents an object interface expected by clients while delegating to a component with a different object interface. Correct adaptation preserves the meaning and order of operations, not merely object method names. Adapting a combined update-and-result operation to separate update and query operations requires storing the last result and specifying its initial value. An adapter should not perform a hidden dummy update just to manufacture that initial observation.
Command-query separation 2026-10-24
Command-query separation distinguishes commands that change state from queries that return observations without changing that state. It permits repeated inspection without further updates, but clients may need a protocol connecting updates and subsequent observations. Combining a command and its resulting observation into one object method can be convenient for transformation object interfaces. Neither organization alone guarantees thread safety.
Object composition 2026-10-24
Object composition builds behavior by storing component software objects and delegating operations to them. A containing software object can expose a distinct object interface and control which operations affect additional state. Exclusive ownership or a specified reference-aliasing discipline may be needed to protect its representation invariant. Unlike subclass overrides, object methods of a separate component do not dynamically dispatch into the containing wrapper.
Use two finite integer lists , with the head of each list representing its stack top. Initially both are empty. A suitable abstract object interface is
trait DoubleStack {
  def pusha(x: Int): Unit
  def pushb(x: Int): Unit
  def popa: Int
  def popb: Int
  def isEmptya: Boolean
  def isEmptyb: Boolean
}
For the unbounded abstract data type, each push has no logical precondition. The postcondition of pusha(x) is and ; pushb(x) symmetrically gives and . The precondition of popa is , and its postcondition is return value , new state , and unchanged . The specification for popb exchanges . Each emptiness query leaves both lists unchanged and returns exactly whether its corresponding list is empty.
These frame conditions matter: an operation on one stack must preserve the other, not merely produce the right return value.
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.
Throw an informative exception, such as NoSuchElementException, and leave the set unchanged. This makes a violated nonempty precondition visible at the point of misuse. Returning a distinguished integer would be ambiguous because every integer is a legitimate set member.
A different object interface could return an optional integer, representing failure explicitly, but that changes the declared return type. For the supplied integer-returning operation, a checked failure is useful and consistent with the stated precondition.
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.
TODO: supply or verify the exact Ewoks practical version used for this examination, including its relevant object interface, implementing classes and call site. The printed paper names the editor but supplies no software class hierarchy or API from which its particular example can be reconstructed.
An earlier official 2015–2016 synopsis identifies Ewoks as a text-editor case study and associates that material with commands represented as software objects and with dynamic binding. That is historical context, not verification of the 2021 practical implementation.[ref]
The method for completing the answer is to identify a common command or editing object interface, two actual implementations from the verified project, and a client call made through the common object interface. Subtype polymorphism would let that client select different behavior without inspecting concrete classes, improving extensibility and separating editing behavior from the code that invokes it. No particular Ewoks software class names or object method signatures are asserted here, so this context-dependent leaf remains incomplete.
Instantiate the two type parameters and implement the function object interface:
val square: Fun[Int, Int] = new Fun[Int, Int] {
  override def apply(x: Int): Int = x * x
}
Parametric polymorphism occurs because the generic trait is specialized by choosing its argument and result types. Subtype polymorphism occurs because the anonymous implementing software class is used through a Fun[Int, Int] reference; calls to its apply operation use the object interface contract and dynamic dispatch. Multiplication itself is not the second polymorphism needed for this explanation.
For values whose mathematical squares fit in a signed 32-bit integer, the result is the ordinary square. Outside that range, the supplied integer return type cannot represent every mathematical square, and the multiplication follows Scala integer-overflow behavior. An implementation requiring exact mathematical results for all integer inputs would need a wider result type or an explicit overflow exception.
Fun[Base, Derived] can safely supply a Fun[Base, Base] contract; Fun[Base, Base] cannot in general supply a Fun[Base, Derived] contract. The input obligation is unchanged, while a derived result satisfies every base-result promise. This is the covariant output rule of function subtyping.
With the invariant printed trait, implement the safe view explicitly:
def widenResult(f: Fun[Base, Derived]): Fun[Base, Base] =
  new Fun[Base, Base] {
    def apply(x: Base): Base = f(x)
  }
Using the illustrative classes from part i, an unsafe substitution would let a client assume a derived-only operation on a result that is merely a base software object:
val broadResult = new Fun[Base, Base] {
  def apply(x: Base): Base = new Base
}
def derivedResultClient(f: Fun[Base, Derived]): Int =
  f(new Base).special()
// derivedResultClient(broadResult) must be rejected.
The direct, variance-aware object interface that would express both safe directions is trait Fun[-S, +T] { def apply(x: S): T }. Its object method consumes the contravariant parameter and produces the covariant parameter. This is an explicitly proposed variant of the printed invariant declaration, not an assumption that its annotations were already present.
Use the decorator pattern. The wrapper implements the same object interface as its component and delegates before changing the output:
class ScaledChannel(inner: Channel, factor: Int) extends Channel {
  override def apply(n: Int): Int = factor * inner(n)
}
It can wrap any channel software object, not only a sum channel. Constructing the wrapper at run time chooses the additional behavior for an existing component, and several wrappers can be composed without adding a subclass for every combination. Each call reaches the wrapped channel once, preserving its state evolution. Scaling the input instead would define a different transformation, particularly for a component that already has history.
Use the adapter pattern, keeping the existing component software object in both directions:
class PipeAsChannel(pipe: Pipe) extends Channel {
  override def apply(n: Int): Int = {
    pipe.consume(n)
    pipe.result
  }
}

class ChannelAsPipe(channel: Channel, initialResult: Int) extends Pipe {
  private var last = initialResult
  override def consume(n: Int): Unit = {
    last = channel(n)
  }
  override def result: Int = last
}
The first adapter feeds the input before reading the pipe's output. The second invokes the channel exactly once per consume and caches its returned output; subsequent result queries do not advance it. Its initial result must be supplied explicitly, because a general channel object interface exposes no initial-output query. Calling the channel on a dummy input during construction would mutate arbitrary state and would invent behavior the object interface does not promise.
These adapters preserve the sequential update/output behavior after each real input, assuming no interfering access to the wrapped component. They do not claim a perfect inverse for every aspect of the original object interfaces: pipe initialization is additional observable state, whereas a channel alone does not specify that observation.
The pipe object interface supports command-query separation: consuming an input changes state, while querying the result observes the current output without feeding another input. A client can inspect the result repeatedly, including an initial result when the concrete pipe defines one. That is useful for accumulated statistics.
The channel object interface instead packages an input update and its associated output into one call. For request-and-result processing this is simpler: the caller cannot forget which update precedes a query. A pipe caller must respect the intended order of operations, and interleaving other callers between consume and result can change the observation. A single channel object method is not automatically thread-safe either; synchronization is a separate property.
Neither object interface is universally better. The choice depends on whether clients need independent observation of state or a combined transformation step. The adapters below can reconcile their protocols, provided the initial-result policy is made explicit.
Software object 2026-10-24
An object combines an identity, state and operations exposed through an object interface. Distinct software objects can hold equal data while remaining different by object identity. Reference aliasing occurs when multiple references designate the same object, so a state update through one reference can affect observations through another.
Subtype polymorphism 2026-10-25
Subtype polymorphism lets a value implementing a more specific type be used through a common supertype object interface. Calls to overridable instance methods can select implementations by dynamic dispatch. Behavioral subtyping additionally requires the concrete implementation to honor the common contract, including preconditions, postconditions and state invariants; structural access to object method names alone does not establish that guarantee.