Abstract class 2026-10-24
An abstract class cannot be instantiated directly and may combine concrete state or behavior with abstract operations that subclasses implement. Concrete object methods can depend on a representation convention that every subclass must maintain. An abstract declaration still has a result type: omitted result types in Scala 2 procedure syntax mean Unit, not an unspecified Boolean query.
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.
Fragile base class problem 2026-10-24
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.
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.
Object constructor 2026-10-24
An operation used when creating and initializing a software class instance. Construction should establish the representation invariant before ordinary clients can access the software object. Class inheritance and initialization order require care if construction invokes overridable object methods before subclass fields are initialized.
Object interface 2026-10-24
The operations and behavioral contract exposed by a software object to its clients. Different classes can implement a common interface while hiding different state representations. Correct substitution requires agreement with the contract, not only matching object method names.
Programming language polymorphism lets one expression or operation work with values of different types or concrete representations. In object-oriented programming, subtype polymorphism lets a reference of an interface or superclass type designate instances of different implementing software classes. A call through that reference uses dynamic dispatch to select the receiver's overriding object method. For example, a routine taking
Book can invoke recall on a linked-list or an array-based implementation while depending only on the common abstract contract.Parametric polymorphism is another form: the type parameter in
FiniteDist[T] lets the same interface describe distributions over Booleans, integers or characters. A generic method introduces a further type parameter when its output type differs. Method overloading, in contrast, supplies several statically selected signatures with one name; it should not be confused with receiver-dependent dynamic dispatch. Correct substitutability also requires implementations to respect the common behavioral contract, not merely to share method names.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]
Object Oriented Programming course synopsis of the University of Oxford (2015-2016). Designing with objects, lectures 6–8: Ewoks, commands as objects, and dynamic binding
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.
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.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.
Garbage collection automatically reclaims allocated memory that is no longer reachable by the running program. Garbage collection roots such as active references and static storage lead through object-reference fields to reachable software objects in heap memory. A tracing garbage collection implementation preserves reachable software objects and can reclaim unreachable ones, including unreachable cycles.
An software object becoming unreachable is different from its state being old or its last object method call having finished. A retained reference can keep it live indefinitely. Collection timing is an implementation matter; automatic reclamation does not promise that an software object is reclaimed immediately when the final useful reference disappears.
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.