Object-Oriented Programming

Chapter 6
Polymorphism, Interfaces, and Dynamic Binding

Bachelors Program in AI & ML
Object-Oriented Programming   •   Bachelor

Polymorphism, Interfaces, and Dynamic Binding

Introduction

Polymorphism is the ability of one programming interface or expression to represent, or operate on, objects of different concrete types. It is one of the central mechanisms through which object-oriented programs achieve extensibility, substitution, and reduced coupling. A method call such as `shape.draw()` can execute different code depending on whether `shape` refers to a circle, rectangle, or another shape. The caller depends on a stable abstraction, while the object supplies the appropriate implementation.

This chapter examines subtype polymorphism, parametric polymorphism, interfaces and protocols, method overloading and overriding, dynamic binding, dependency inversion, and duck or structural typing. Examples use Java-like syntax, with selected Python examples where dynamic and structural typing are especially clear. The ideas apply broadly across languages such as Java, C++, C#, Python, TypeScript, Kotlin, and Swift.

Polymorphism is particularly important in AI and machine-learning software. A training pipeline may accept different data loaders; a model evaluator may work with multiple model types; and a deployment service may invoke different prediction strategies through a common interface. Good polymorphic design allows new models or data sources to be added without rewriting stable orchestration code.

Learning Objectives

By the end of this chapter, you should be able to:

  • Explain the purpose and forms of polymorphism.
  • Distinguish interfaces, abstract classes, protocols, and concrete classes.
  • Differentiate overloading from overriding.
  • Trace static method selection and dynamic dispatch.
  • Apply subtype and parametric polymorphism in program design.
  • Use dependency inversion to reduce coupling.
  • Compare nominal typing with duck or structural typing.
  • Identify limitations, design risks, and testing obligations associated with polymorphism.

1. Foundations of Polymorphism

The word polymorphism means “many forms.” In programming, it describes a common operation that can be applied to values of different types, or a common reference that can denote objects with different implementations.

Consider the following hierarchy:

```java

abstract class Shape {

abstract double area();

}

class Circle extends Shape {

private double radius;

Circle(double radius) { this.radius = radius; }

@Override

double area() {

return Math.PI radius radius;

}

}

class Rectangle extends Shape {

private double width, height;

Rectangle(double width, double height) {

this.width = width;

this.height = height;

}

@Override

double area() {

return width * height;

}

}

```

A method can now accept the abstraction rather than every concrete class:

```java

double totalArea(List<Shape> shapes) {

double total = 0;

for (Shape shape : shapes) {

total += shape.area();

}

return total;

}

```

The loop does not need to know whether each object is a circle or rectangle. The expression `shape.area()` is resolved at run time to the implementation belonging to the actual object. This property is called dynamic dispatch or dynamic binding.

Polymorphism should not be confused with inheritance alone. Inheritance can support subtype polymorphism, but polymorphism can also arise through interfaces, generic types, function values, and structural typing.

1.1 Essential terminology

A supertype is a general type, such as `Shape`. A subtype is a more specific type, such as `Circle`. A concrete type can normally be instantiated. An abstract type specifies capabilities or shared behavior without necessarily providing complete implementation. A polymorphic reference has a declared type that is more general than the object it refers to.

The static type of an expression is the type known by the compiler. The dynamic type, also called the run-time type, is the actual type of the object during execution. The static type controls which operations are legally available; the dynamic type commonly controls which overridden implementation executes.

2. Subtype Polymorphism and the Substitution Principle

Subtype polymorphism allows an instance of a subtype to be used wherever its supertype is expected. This is useful only when the subtype genuinely conforms to the behavioral expectations of the supertype.

The Liskov Substitution Principle states that objects of a subtype should be usable in place of objects of the supertype without invalidating desirable properties of the program. The principle is behavioral rather than merely syntactic. A class may inherit methods and still violate the expectations of its base class.

For example, if `Rectangle` provides independently settable width and height, deriving `Square` from it can cause problems. Code that expects changing the width to leave the height unchanged may fail for a square. Thus, a mathematically related concept is not automatically a sound subtype relationship.

A practical subtype contract has three parts:

1. Preconditions: requirements before an operation. A subtype should not require more restrictive inputs than the supertype.

2. Postconditions: guarantees after an operation. A subtype should preserve or strengthen the guarantees expected by clients.

3. Invariants: properties that remain true throughout the object’s valid lifetime.

Subtype polymorphism is therefore a design commitment. It communicates that clients may safely rely on the abstraction’s contract rather than on implementation details.

2.1 Abstract classes and interfaces

An abstract class can define common state, implemented methods, constructors, and abstract operations. It is appropriate when related classes share implementation or a conceptual identity.

An interface primarily defines a capability or contract. Modern languages may permit default methods, static methods, or limited state, but an interface generally emphasizes what an object can do rather than what it is.

```java

interface Predictor {

Prediction predict(DataPoint input);

}

class LinearPredictor implements Predictor {

public Prediction predict(DataPoint input) {

// Linear model computation

return new Prediction();

}

}

class TreePredictor implements Predictor {

public Prediction predict(DataPoint input) {

// Decision-tree traversal

return new Prediction();

}

}

```

A caller can depend on `Predictor` and remain independent of the selected model implementation. Interfaces are especially valuable when implementations are supplied by different teams, libraries, or deployment configurations.

2.2 Interface segregation and capability design

A large interface forces clients to depend on operations they do not use. The Interface Segregation Principle recommends several focused interfaces instead of one broad interface.

For example, separate `Readable`, `Writable`, and `Closable` capabilities may be preferable to an `AdvancedFileDevice` interface containing every operation. Small interfaces improve substitution, testing, and reuse, but excessive fragmentation can make a design difficult to understand. Interfaces should represent cohesive responsibilities, not arbitrary collections of methods.

3. Overloading, Overriding, and Hiding

These mechanisms are often confused because they all involve methods with related names.

3.1 Overloading

Overloading defines multiple methods with the same name but different parameter lists in one class or related scope:

```java

class Printer {

void print(String value) { }

void print(int value) { }

void print(String value, int copies) { }

}

```

The compiler selects an overload using the compile-time types and number of arguments. Overloading is therefore generally a form of compile-time, or static, polymorphism. Return type alone is not sufficient to distinguish overloads in Java-like languages.

Overloading can improve readability when operations have the same conceptual purpose. However, too many overloads can create ambiguity, especially with implicit conversions, `null`, optional arguments, or inheritance.

3.2 Overriding

Overriding occurs when a subtype supplies a replacement implementation for an inherited method with a compatible signature:

```java

class Animal {

void speak() { System.out.println("sound"); }

}

class Dog extends Animal {

@Override

void speak() { System.out.println("bark"); }

}

```

If an `Animal` reference refers to a `Dog`, calling `speak()` normally executes `Dog.speak()`. This is run-time polymorphism.

A valid override must respect language rules regarding method name, parameters, visibility, return-type compatibility, exceptions, and modifiers. The `@Override` annotation is a best practice because it lets the compiler detect accidental signature mismatches.

3.3 Static methods and fields

Static methods and fields belong to a class rather than an object. In many languages, they are hidden rather than dynamically overridden. A call through a class name makes this explicit. Developers should not expect instance-style dynamic dispatch for static members.

3.4 Overriding and contracts

An overriding method should preserve the abstraction’s contract. Changing an implementation is legitimate; silently changing meaning is not. For example, a subtype of `Cache` should not unexpectedly make `get()` destructive if clients expect reads to be non-destructive.

4. Dynamic Binding and Dispatch

Dynamic binding connects a method call to an implementation at run time. The following example illustrates the distinction between static and dynamic information:

```java

Animal a = new Dog();

a.speak();

```

The compiler checks the static type `Animal` and confirms that `speak()` is available. During execution, the run-time type `Dog` determines the overridden implementation. Thus:

  • The static type determines what the caller is allowed to request.
  • The dynamic type determines which overridden behavior is executed.

This separation supports extensibility. A function compiled before a new subtype existed can often operate correctly with that subtype, provided it satisfies the abstraction.

4.1 A dispatch trace

Suppose:

```java

interface PaymentMethod {

void authorize(double amount);

}

class CardPayment implements PaymentMethod {

public void authorize(double amount) {

System.out.println("Card authorization");

}

}

class WalletPayment implements PaymentMethod {

public void authorize(double amount) {

System.out.println("Wallet authorization");

}

}

```

Then:

```java

PaymentMethod method = new WalletPayment();

method.authorize(50.0);

```

The compiler verifies that `PaymentMethod` declares `authorize`. At run time, the dispatch mechanism identifies the object as a `WalletPayment` and invokes `WalletPayment.authorize`. The caller remains unaware of the concrete payment class.

Dynamic dispatch may be implemented through virtual method tables, method handles, runtime metadata, or other mechanisms. The precise implementation is language- and compiler-dependent. Its conceptual effect is stable: a call through an abstraction selects compatible behavior associated with the actual object.

4.2 Benefits and costs

Dynamic dispatch provides extensibility, modularity, and separation of concerns. It also introduces costs: indirection can complicate debugging, excessive abstraction can obscure control flow, and incorrect contracts can produce run-time failures. Modern compilers and runtimes may optimize calls through techniques such as inlining or speculative devirtualization, but programmers should design for clarity rather than assume a particular optimization.

5. Parametric Polymorphism and Generics

Parametric polymorphism allows code to be written once and operate uniformly over a family of types. Generics are the principal implementation of this idea in languages such as Java, C#, and TypeScript.

```java

class Box<T> {

private T value;

Box(T value) { this.value = value; }

T get() { return value; }

void set(T value) { this.value = value; }

}

Box<String> names = new Box<>("Ada");

Box<Integer> count = new Box<>(3);

```

The class is parameterized by `T`. Its operations do not depend on a particular concrete type. Static type checking preserves useful guarantees: a `Box<String>` cannot normally be assigned an integer.

A generic method can express reusable algorithms:

```java

static <T> T first(List<T> values) {

if (values.isEmpty()) throw new IllegalArgumentException();

return values.get(0);

}

```

Parametric polymorphism differs from subtype polymorphism. Subtype polymorphism relies on a shared behavioral relationship, while parametric polymorphism abstracts over types uniformly. A generic sorting algorithm may work for any `T` satisfying an ordering constraint, whereas a `Shape` collection relies on a common `area()` operation.

5.1 Bounded type parameters

A bound restricts a type parameter to values supporting a required capability:

```java

static <T extends Comparable<T>> T maximum(List<T> values) {

T result = values.get(0);

for (T value : values) {

if (value.compareTo(result) > 0) result = value;

}

return result;

}

```

The method requires `T` to implement `Comparable<T>`. Without that constraint, the compiler could not verify that `compareTo` exists.

5.2 Variance and safe collection use

In languages with generic variance, a distinction is made between covariance, contravariance, and invariance. In Java, `List<Dog>` is not a subtype of `List<Animal>` because allowing arbitrary animals into it would violate its element type. A read-only view can be expressed with an upper-bounded wildcard:

```java

void displayAll(List<? extends Animal> animals) { }

```

This method can read animals from a list of animals or dogs, but it should not insert an arbitrary `Animal`. A lower-bounded form such as `List<? super Dog>` is useful when safely adding dogs. A practical guideline is “producer extends, consumer super,” though exact syntax differs across languages.

6. Duck Typing and Structural Typing

Nominal typing determines compatibility through explicit declarations: a class says that it implements an interface or extends a base class. Structural typing determines compatibility from the presence and types of required members. Duck typing is a dynamic form of structural reasoning, summarized as: if an object behaves like the required type, it can be used as that type.

Python example:

```python

def save_report(writer, report):

writer.write(report)

```

The function does not require a declared `Writer` base class. Any object with a suitable `write` method may work:

```python

class FileWriter:

def write(self, report):

pass

class NetworkWriter:

def write(self, report):

pass

```

Python can also express a checkable protocol using `typing.Protocol`:

```python

from typing import Protocol

class Writer(Protocol):

def write(self, report: str) -> None: ...

def save_report(writer: Writer, report: str) -> None:

writer.write(report)

```

Static analysis tools can verify structural compatibility even though `FileWriter` and `NetworkWriter` need not explicitly inherit from `Writer`.

Structural typing promotes decoupling and interoperability, but it may move errors from compilation to execution when required behavior is absent or semantically incompatible. Matching a method name is not sufficient if the method’s contract differs. Documentation, tests, and explicit protocols remain important.

7. Dependency Inversion and Polymorphic Design

Dependency inversion is a design principle with two related ideas:

1. High-level policy should not depend directly on low-level implementation details; both should depend on abstractions.

2. Abstractions should not depend on details; details should depend on abstractions.

A tightly coupled service might construct a concrete model directly:

```java

class EvaluationService {

private RandomForestModel model = new RandomForestModel();

}

```

This design makes replacement and testing difficult. A polymorphic alternative is dependency injection:

```java

class EvaluationService {

private final Predictor predictor;

EvaluationService(Predictor predictor) {

this.predictor = predictor;

}

Metrics evaluate(DataSet data) {

// use predictor.predict(...) for each example

return new Metrics();

}

}

```

The service depends on `Predictor`, while the application supplies a concrete implementation. A production system may inject a neural network predictor; a test may inject a deterministic fake; an experiment may inject a decision tree.

Dependency inversion is not a demand that every class have an interface. An abstraction is valuable when there are meaningful alternatives, a volatile dependency, a boundary between components, or a need for independent testing. Creating interfaces mechanically can increase complexity without reducing coupling.

8. Worked Example: Extensible Feature Transformation

Suppose an AI pipeline must transform input records before model inference. Different deployments require normalization, tokenization, or missing-value imputation.

Define a common contract:

```java

interface Transformer {

Record transform(Record input);

}

```

Implementations provide specialized behavior:

```java

class Normalizer implements Transformer {

public Record transform(Record input) {

return input.normalized();

}

}

class Imputer implements Transformer {

public Record transform(Record input) {

return input.withMissingValuesFilled();

}

}

```

The pipeline depends on the abstraction:

```java

class Pipeline {

private final List<Transformer> steps;

Pipeline(List<Transformer> steps) {

this.steps = steps;

}

Record process(Record input) {

Record current = input;

for (Transformer step : steps) {

current = step.transform(current);

}

return current;

}

}

```

A client can configure:

```java

Pipeline pipeline = new Pipeline(

List.of(new Imputer(), new Normalizer())

);

```

The pipeline uses subtype polymorphism: every concrete transformer can be treated as a `Transformer`. It also demonstrates dependency inversion: the pipeline does not construct or depend on particular transformation algorithms. Adding `Tokenizer` requires a new class and configuration, not a modification to the processing loop.

The design has obligations. Transformations must preserve the `Record` contract, ordering must be documented because transformations may not commute, and failures must be specified. For example, an imputer may guarantee that no missing values remain, while a normalizer may require numeric fields. The interface alone cannot express all semantic constraints; tests and documentation must do so.

9. Illustrative Case Study: Pluggable Model Evaluation

Background

A university research lab evaluates classification models on a common validation dataset. Initially, an evaluation program directly creates a particular support-vector machine implementation. Researchers must edit the evaluator whenever they want to test a random forest or neural network.

Problem

The evaluator is tightly coupled to one model class. This causes duplicated code, difficult testing, and a risk that model-specific logic will leak into metric computation. The team also wants to compare models reproducibly.

Analysis

The stable concept is prediction: given an input, a model returns a prediction. The evaluator should depend on that capability, not on training algorithms or storage details. A nominal interface is appropriate because the lab wants an explicit contract concerning input shape, output labels, and error behavior.

```java

interface Classifier {

Label predict(Features input);

}

class Evaluator {

private final Classifier classifier;

Evaluator(Classifier classifier) {

this.classifier = classifier;

}

ConfusionMatrix evaluate(List<Example> examples) {

ConfusionMatrix result = new ConfusionMatrix();

for (Example example : examples) {

Label predicted = classifier.predict(example.features());

result.record(example.label(), predicted);

}

return result;

}

}

```

Approach and solution

Each model adapter implements `Classifier`. The evaluator is constructed with a selected implementation. A fake classifier is used in unit tests to verify confusion-matrix logic independently of model training. Configuration identifies the model and its version, while the evaluator records the configuration with the metrics.

Outcome

Researchers can add model adapters without changing metric code. Tests become faster and more focused. The architecture also supports a remote classifier, provided its adapter satisfies the same contract. However, the team documents that prediction latency, probabilistic outputs, and preprocessing requirements may differ between implementations.

Learning points

  • A small, stable interface isolates policy from implementation.
  • Dependency injection enables substitution and testing.
  • Polymorphism does not eliminate domain differences; those differences must be represented in contracts or separate capabilities.
  • Reproducibility requires recording model versions, preprocessing configuration, and data versions, not merely the interface type.

10. Testing Polymorphic Systems

Polymorphic code should be tested at several levels. Unit tests verify each implementation’s local behavior. Contract tests apply the same behavioral tests to every implementation of an interface. Integration tests verify that actual components communicate correctly.

For example, every `Classifier` implementation might be required to:

  • reject malformed feature vectors consistently;
  • return a label from the permitted label set;
  • behave deterministically when configured with a fixed seed, if determinism is part of the contract;
  • document whether prediction is thread-safe.

Property-based testing can check general rules over many generated inputs. Mutation testing can reveal whether tests actually detect incorrect overrides. Mocks and fakes are useful, but overuse can make tests verify implementation assumptions rather than externally visible behavior.

Dynamic dispatch also complicates coverage analysis. A test that exercises only one implementation does not establish that another implementation obeys the same contract. Teams should identify all supported implementations and test substitution explicitly.

11. Challenges, Limitations, and Design Risks

Abstraction leakage

An interface may expose operations that are meaningful only for some implementations. If clients repeatedly cast objects to concrete classes, the abstraction is probably inadequate or the design is using the wrong common type.

Fragile base classes

Changes to a base class can unintentionally affect many subclasses. Prefer small contracts, carefully documented extension points, and composition where inheritance does not express a genuine substitutable relationship.

Accidental overriding

A misspelled method or altered parameter list may create a new method instead of overriding the intended one. Compiler annotations and static analysis reduce this risk.

Performance and resource behavior

Dynamic dispatch adds indirection, and different implementations may have radically different time, memory, or network costs. A common interface should document relevant nonfunctional properties when clients depend on them.

Semantic incompatibility

Two classes may have the same method signature but different meanings. Structural compatibility is not behavioral compatibility. Contracts, examples, invariants, and tests are essential.

Excessive generality

An interface introduced “just in case” can freeze an unstable design. Begin with a clear responsibility and introduce abstractions where variation or dependency boundaries justify them.

12. Professional, Ethical, and AI-System Considerations

Polymorphism influences governance because interchangeable components may not be ethically interchangeable. In an AI system, replacing one classifier with another can change error rates across demographic groups, explainability, privacy exposure, or regulatory compliance. An interface such as `Classifier` should not imply that all models have equivalent safety properties.

Production systems should record component identity, version, configuration, training-data lineage, and applicable validation results. Dynamic plugin loading requires authentication, authorization, dependency scanning, and sandboxing where untrusted code may be introduced. A plugin that satisfies a syntactic interface could still exfiltrate data or produce unsafe outputs.

For high-impact applications, contracts should include operational requirements such as audit logging, human review, failure handling, and monitoring for distribution shift. Substitutability must therefore be evaluated at both the software-behavior level and the system-risk level.

13. Best Practices

1. Design interfaces around cohesive client needs.

2. Prefer composition and dependency injection when inheritance is not a genuine subtype relationship.

3. Keep abstractions stable and implementations replaceable.

4. Use explicit override annotations and compiler checks.

5. Document behavioral contracts, error conditions, performance, and thread-safety expectations.

6. Distinguish compile-time overload selection from run-time overriding.

7. Use generics to express type-independent algorithms without sacrificing type safety.

8. Use structural typing when interoperability is valuable, but supplement it with protocols and contract tests.

9. Avoid downcasting unless the design explicitly requires a safely identified variant.

10. Test every supported implementation against shared behavioral requirements.

11. In AI systems, record model and preprocessing identity rather than treating implementations as interchangeable by default.

12. Introduce abstraction when it reduces a real dependency or represents a meaningful capability.

Contemporary Developments

Modern languages increasingly combine object-oriented and functional features. First-class functions, algebraic data types, traits, extension methods, type classes, and protocol-oriented programming provide alternatives or complements to classical inheritance. Static analyzers can check structural protocols, while dependency-injection frameworks automate object composition.

Cloud and AI platforms commonly use plugin architectures, model registries, and service interfaces. These systems make polymorphism operationally significant: a component may be local, remote, hardware-accelerated, or supplied by a third party. Consequently, interface design now includes serialization formats, version compatibility, observability, security, and failure semantics in addition to method signatures.

Chapter Summary

Polymorphism enables one abstraction to work with multiple implementations. Subtype polymorphism supports substitution through inheritance or interfaces, but valid substitution requires behavioral compatibility, not merely matching syntax. Interfaces and abstract classes define reusable contracts, while focused interfaces reduce unnecessary coupling. Overloading is generally resolved at compile time; overriding is selected through run-time dynamic dispatch. Parametric polymorphism, implemented through generics, allows algorithms and data structures to operate over type parameters while preserving type safety. Duck and structural typing judge compatibility by available behavior and can improve interoperability, although semantic contracts remain necessary. Dependency inversion places stable abstractions between high-level policies and replaceable details, enabling dependency injection and effective testing. In AI and professional systems, polymorphic substitution must also consider performance, security, fairness, reproducibility, and governance.

Key Terms

Polymorphism: The ability of one interface, operation, or abstraction to work with multiple types or implementations.

Subtype: A type that conforms to the contract of a more general supertype.

Supertype: A general type that describes common capabilities or behavior.

Static type: The type known by the compiler for an expression or reference.

Dynamic type: The actual run-time type of an object.

Dynamic dispatch: Run-time selection of an overridden method implementation.

Dynamic binding: Associating a method call with its implementation during execution.

Overloading: Defining methods with the same name but different parameter lists.

Overriding: Replacing an inherited method implementation in a subtype.

Interface: An explicit contract describing operations a class agrees to provide.

Abstract class: A non-final class that may combine shared implementation with incomplete operations.

Parametric polymorphism: Writing code generically over type parameters.

Generic: A class, method, or data structure parameterized by one or more types.

Nominal typing: Type compatibility based on explicit declarations such as inheritance or implementation.

Structural typing: Type compatibility based on members and operations a type provides.

Duck typing: Dynamic structural typing based on whether an object supports required behavior.

Dependency inversion: A principle that makes high-level policy depend on abstractions rather than concrete details.

Dependency injection: Supplying an object’s dependencies from outside rather than constructing them internally.

Liskov Substitution Principle: The requirement that subtypes preserve the behavioral expectations of their supertypes.

Contract test: A test applied to implementations to verify a shared abstraction contract.

Review Questions

Short Answer Questions

1. What is the difference between a static type and a dynamic type?

2. Distinguish method overloading from method overriding.

3. Why is behavioral compatibility necessary for subtype polymorphism?

4. What problem does dependency injection address?

5. How does structural typing differ from nominal typing?

Long Answer Questions

1. Explain dynamic dispatch using a suitable class or interface example, and trace what happens during compilation and execution.

2. Compare abstract classes and interfaces. Identify situations in which each is an appropriate design choice.

3. Explain parametric polymorphism and bounded generics, including their contribution to type safety.

4. Discuss the benefits and risks of duck typing in a large software project.

5. Explain how dependency inversion and polymorphism can improve the architecture and testability of an AI evaluation pipeline.

Analytical / Application Questions

1. A `ReportGenerator` directly constructs a `PdfRenderer` and cannot support HTML output without modification. Redesign the situation using an abstraction and dependency injection, and justify your design.

2. A subclass overrides `withdraw()` so that it rejects every withdrawal, although the base account contract permits withdrawals when sufficient funds exist. Analyze whether this is a valid subtype and explain how you would redesign the hierarchy.

3. An AI platform allows any plugin implementing `predict(input)` to replace the production model. Identify at least four technical or governance checks required before treating two plugins as safely interchangeable.

Multiple Choice Questions with Answers

Question 1

Which mechanism normally selects an overridden instance method at run time?

A. Method overloading

B. Dynamic dispatch

C. Generic type inference

D. Constructor chaining

Correct Answer: B. Dynamic dispatch

Question 2

What primarily distinguishes method overloading from overriding?

A. Overloading uses different parameter lists, while overriding replaces inherited behavior

B. Overloading always occurs at run time, while overriding always occurs at compile time

C. Overloading requires an interface, while overriding cannot use inheritance

D. Overloading changes fields, while overriding changes constructors

Correct Answer: A. Overloading uses different parameter lists, while overriding replaces inherited behavior

Question 3

What does the Liskov Substitution Principle require?

A. Every class must have exactly one parent

B. Subtypes must preserve the behavioral expectations of their supertypes

C. All methods must be static

D. Generic types must be invariant

Correct Answer: B. Subtypes must preserve the behavioral expectations of their supertypes

Question 4

Which example best illustrates parametric polymorphism?

A. A subclass changes the implementation of an inherited method

B. A method is selected from two overloads using argument types

C. A generic list algorithm operates over an arbitrary element type

D. A class hides a static field from its parent

Correct Answer: C. A generic list algorithm operates over an arbitrary element type

Question 5

In a nominal type system, compatibility is primarily established by what?

A. Matching method names at run time only

B. Explicit declarations such as inheritance or interface implementation

C. Identical memory layouts

D. The number of object fields

Correct Answer: B. Explicit declarations such as inheritance or interface implementation

Question 6

Which design most directly applies dependency inversion?

A. A high-level service constructs a concrete database driver internally

B. A high-level service receives a repository abstraction through its constructor

C. Every class inherits from a universal base class

D. A method uses several overloaded constructors

Correct Answer: B. A high-level service receives a repository abstraction through its constructor

Question 7

What is a major risk of structural or duck typing?

A. It prevents any form of code reuse

B. It guarantees semantic compatibility automatically

C. Some incompatibilities may be discovered only at run time or through testing

D. It cannot represent interfaces

Correct Answer: C. Some incompatibilities may be discovered only at run time or through testing

Question 8

Why is `@Override` or an equivalent compiler annotation recommended?

A. It makes every method private

B. It forces static dispatch

C. It helps detect accidental signature mismatches

D. It converts an abstract class into an interface

Correct Answer: C. It helps detect accidental signature mismatches

Question 9

A variable declared as `Animal` refers to a `Dog`, and `Dog` overrides `speak()`. Which implementation normally executes for `animal.speak()`?

A. The implementation in `Animal` is always selected

B. The implementation in `Dog` is selected through dynamic dispatch

C. The compiler selects randomly between the two

D. No method can execute through a supertype reference

Correct Answer: B. The implementation in `Dog` is selected through dynamic dispatch

Question 10

Which practice is most appropriate for an AI plugin architecture?

A. Assume all implementations are equivalent if their method signatures match

B. Record model versions, configurations, and validation properties before substitution

C. Avoid testing implementations that share an interface

D. Allow untrusted plugins unrestricted access to production data

Correct Answer: B. Record model versions, configurations, and validation properties before substitution

Object-Oriented Programming
Chapter 6 of 15