Skip to main content

Sample Solutions & Explanations (Set B)

Study Reference

These sample solutions provide standard answers, design justifications, refactored code patterns, and marking rubrics for COMP2511 26T2 Sample Exam - Set B.


Section 1: Multiple Choice Solutions

QuestionAnswerKey Rationale
Question 1BAggregation: Songs have independent lifecycles from playlists; deleting a playlist does not destroy the songs in the catalogue.
Question 2BPECS / Covariance: List<? extends Animal> allows covariance reading (List<Dog> is valid because Dog extends Animal).
Question 3BExceptions: Checked exceptions (subclasses of Exception excluding RuntimeException) must be caught or declared with throws.
Question 4CFunctional Interface: Predicate<T> accepts an argument of type T and returns a boolean value (Order -> boolean).
Question 5DDIP: High-level module CheckoutService directly depends on low-level concrete implementations rather than abstractions/interfaces.
Question 6BObserver Pattern: Defines a one-to-many dependency so that when one subject changes state, all its registered observers are notified dynamically.
Question 7CServerless: Ideal for event-driven, irregular, stateless compute tasks (image resizing) without provisioning persistent server infrastructure.
Question 8BMicroservices Trade-offs: Increases distributed systems complexity (network latency, eventual consistency, distributed tracing, complex deployment).

Section 2: Short Answer Solutions

Question 9: Design by Contract & Liskov Substitution Principle (4 marks)

Answer:

PremiumAccount is not a valid behavioural subtype of Account under Design by Contract and LSP.

Justification:

  1. Preconditions Violation (2 marks):

    • Rule: A subtype method may only weaken (or keep equal) preconditions, never strengthen them.
    • Analysis: The base class accepts any amount > 0 (e.g. amount = 50). The subclass restricts input to amount >= 100. A client expecting an Account could pass $50 and have their expectation broken by PremiumAccount, violating substitutability.
  2. Postconditions Violation (2 marks):

    • Rule: A subtype method may only strengthen (or keep equal) postconditions, never weaken them.
    • Analysis: The base class guarantees the balance decreases by exactly amount. The subclass only promises a decrease of at least amount (allowing arbitrary extra fees or over-deductions), which weakens the contract guarantee.

Question 10: Java Generics Invariance (4 marks)

Answer:

  1. Why it fails to compile (2 marks):

    • Java generic types are invariant. Even though Dog is a subtype of Animal, List<Dog> is not a subtype of List<Animal>.
    • If Java allowed List<Animal> animals = dogs;, one could subsequently execute animals.add(new Cat());, which would insert a Cat into what is actually a List<Dog>, compromising runtime type safety (Heap Pollution).
  2. Revised parameter type (2 marks):

    • Use an upper-bounded wildcard:
      void inspect(List<? extends Animal> animals)
    • Justification: The upper-bounded wildcard allows covariance for read-only operations (get() returns Animal). The compiler safely allows passing List<Dog>, List<Cat>, or List<Animal>, while preventing invalid element insertions.

Question 11: Code & Design Smells in ReportExporter (5 marks)

Answer:

  1. Smell 1: Switch Statement / Conditional Formatting Logic (Violation of Open-Closed Principle) (2.5 marks)

    • Problem: Adding a new format (e.g., YAML, PDF) requires modifying export() with additional else if branches, risking regression in existing formats.
    • Refactoring: Apply the Strategy Pattern or Polymorphic Exporters. Create an interface ReportFormatStrategy with a method String format(Report report), and instantiate specific implementations (CsvReportStrategy, JsonReportStrategy, XmlReportStrategy).
  2. Smell 2: Long Method / Lack of Cohesion / Mixed Concerns (2.5 marks)

    • Problem: ReportExporter mixes data extraction, formatting string concatenation, and auditing logic (audit(...)).
    • Refactoring: Extract auditing into a decorator or separate auditing service. Delegate string serialization to dedicated serializer helper classes.

Question 12: Design Pattern - Delivery Add-ons (4 marks)

Answer:

  • Chosen Pattern: Decorator Pattern (Structural).
  • Mapping & Justification:
    • Component Interface: DeliveryService defining double calculateCost() and void executeDelivery().
    • Concrete Component: StandardDelivery providing base delivery behavior and pricing.
    • Base Decorator: DeliveryOptionDecorator implementing DeliveryService and wrapping a DeliveryService reference.
    • Concrete Decorators: SignatureOnDeliveryDecorator, InsuranceDecorator, RefrigeratedHandlingDecorator, GiftPackagingDecorator.
    • Intent Match: Decorators attach additional responsibilities and costs to objects dynamically at runtime, avoiding class explosion from having $2^4 = 16$ static subclasses for all permutations.

Question 13: Design Pattern - Course Hierarchy (5 marks)

Answer:

  • Chosen Pattern: Composite Pattern (Structural).
  • Mapping & Justification:
    • Component Abstraction: CourseItem (interface or abstract class) declaring uniform operations: int getEstimatedStudyTime() and void display(int indentLevel).
    • Leaf: Lesson representing individual terminal units of learning content without children. getEstimatedStudyTime() returns its own duration.
    • Composite: Module containing a collection of child CourseItem instances (which can be Lessons or nested Modules). getEstimatedStudyTime() iterates through children and sums their times recursively.
    • Intent Match: Allows clients to treat individual objects (Lesson) and compositions of objects (Module) uniformly when rendering, traversing, and aggregating metrics across the whole course tree.

Section 3: Design & Programming Solutions

Question 14: Makerspace Domain Model

package q14;

import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;

// 1. Makerspace contains Rooms (Composition: 1 to 1..*)
// Makerspace has 1 Staff Coordinator (Aggregation: 1 to 1)
// Makerspace owns Equipment (Aggregation/Composition: 1 to 0..*)
public class Makerspace {
private final String id;
private String name;
private StaffMember coordinator; // 1 Coordinator
private final List<Room> rooms = new ArrayList<>(); // 1..* Rooms
private final List<Equipment> equipmentInventory = new ArrayList<>(); // 0..* Equipment

public Makerspace(String id, String name, StaffMember coordinator) {
this.id = id;
this.name = name;
this.coordinator = coordinator;
}
// Getters and helper methods...
}

// 2. Room belongs to 1 Makerspace.
// Room contains Workstations (Composition: 1 to 0..* - workstation dies with room).
// Room has 1 Staff Supervisor (Aggregation: 1 to 1).
public class Room {
private final String id;
private String name;
private StaffMember supervisor; // 1 Supervisor
private final List<Workstation> workstations = new ArrayList<>(); // 0..* Workstations
private final List<Equipment> assignedEquipment = new ArrayList<>(); // 0..* Equipment currently in room

public Room(String id, String name, StaffMember supervisor) {
this.id = id;
this.name = name;
this.supervisor = supervisor;
}
}

// 3. Workstation belongs to exactly 1 Room.
// Workstation has 0..* Reservations over time.
public class Workstation {
private final String id;
private final String type;
private final List<Reservation> reservations = new ArrayList<>();

public Workstation(String id, String type) {
this.id = id;
this.type = type;
}
}

// 4. Reservation links exactly 1 Student and 1 Workstation (1 to 1 per reservation).
public class Reservation {
private final String reservationId;
private final Student student; // 1 Student
private final Workstation workstation; // 1 Workstation
private final LocalDateTime startTime;
private final LocalDateTime endTime;

public Reservation(String reservationId, Student student, Workstation workstation,
LocalDateTime startTime, LocalDateTime endTime) {
this.reservationId = reservationId;
this.student = student;
this.workstation = workstation;
this.startTime = startTime;
this.endTime = endTime;
}
}

// 5. Persons & Staff
public abstract class Person {
private final String id;
private final String name;
public Person(String id, String name) { this.id = id; this.name = name; }
}

public class StaffMember extends Person {
public StaffMember(String id, String name) { super(id, name); }
}

public class Student extends Person {
private final List<Reservation> reservations = new ArrayList<>();
private final List<Equipment> checkedOutEquipment = new ArrayList<>();
public Student(String id, String name) { super(id, name); }
}

// 6. Equipment can be moved between rooms and checked out by students.
public class Equipment {
private final String id;
private final String type;
private Room currentRoom;
private Student currentBorrower; // null if available

public Equipment(String id, String type) {
this.id = id;
this.type = type;
}
}

Question 15: ParcelFlow Refactoring

Part A: Pricing Policies (Strategy Pattern)

// Strategy Interface
public interface PricingStrategy {
double calculate(double weightKg, double distanceKm);
}

public class StandardPricingStrategy implements PricingStrategy {
@Override
public double calculate(double weightKg, double distanceKm) {
return 5.0 + (1.25 * weightKg);
}
}

public class ExpressPricingStrategy implements PricingStrategy {
@Override
public double calculate(double weightKg, double distanceKm) {
double remoteSurcharge = distanceKm > 20.0 ? 5.0 : 0.0;
return 10.0 + (2.0 * weightKg) + remoteSurcharge;
}
}

public class SameDayPricingStrategy implements PricingStrategy {
@Override
public double calculate(double weightKg, double distanceKm) {
return 20.0 + (3.0 * weightKg) + (0.5 * distanceKm);
}
}

public class GreenPricingStrategy implements PricingStrategy {
@Override
public double calculate(double weightKg, double distanceKm) {
return 4.0 + (1.0 * weightKg) + (0.2 * distanceKm);
}
}

Part B: Status Notifications (Observer Pattern)

public interface StatusListener {
void onStatusChange(String parcelId, ParcelStatus oldStatus, ParcelStatus newStatus);
}

// Inside Parcel.java
private final List<StatusListener> listeners = new ArrayList<>();

public void subscribe(StatusListener listener) {
if (!listeners.contains(listener)) {
listeners.add(listener);
}
}

public void unsubscribe(StatusListener listener) {
listeners.remove(listener);
}

public void updateStatus(ParcelStatus newStatus) {
ParcelStatus oldStatus = status;
status = newStatus;
statusHistory.add(newStatus);

for (StatusListener listener : listeners) {
listener.onStatusChange(id, oldStatus, newStatus);
}
}

Section 4: Software Architecture Solutions

Question 16: Architectural Decision Record (ADR) (8 marks)

1. Important Architectural Characteristics:

  1. Read Performance / Latency vs Data Consistency: Course catalogue searches during peak enrolment periods demand sub-second read performance and flexible querying over semi-structured course descriptors, while primary enrolment states require ACID transaction consistency.
  2. Operational Simplicity / Maintainability: The engineering team has constrained capacity; managing dual database synchronisation (CQRS dual-writes / CDC pipelines) introduces operational overhead and failure modes.

2. Architecture Decision Record (ADR):

# ADR 003: Retain Relational Database with Read-Optimised Views for Course Catalogue

## Status
Accepted

## Context
The course search catalogue is read frequently during peak enrolment periods and contains irregular metadata. The team considered splitting the catalogue into a separate document database (e.g. MongoDB). However, enrolment transactions require strict ACID consistency, and the 6-person team has limited operational capability to manage distributed synchronisation pipelines.

## Decision
We will retain the existing PostgreSQL relational database rather than provisioning a separate document database. We will support irregular course catalogue metadata using native JSONB columns with GIN indexes, combined with indexed materialized views for search queries.

## Consequences
- Positive: Eliminates dual-write inconsistency and avoids operational overhead of maintaining two database engines.
- Positive: Preserves strong transactional guarantees across course enrolment transactions.
- Negative: Query performance for highly nested full-text document searches may be marginally lower than a dedicated search engine, but remains well within current traffic targets.

Question 17: Architecture Selection (10 marks)

Chosen Architecture: Modular Monolith

Justification:

  1. Domain Alignment & Modularity: The system has clear bounded contexts (Accounts, Invoicing, Inventory, Reporting, Notifications). A Modular Monolith enforces explicit package/module interfaces in a single deployable unit, preventing spaghetti code while avoiding distributed network calls.
  2. Team Size & Operational Simplicity: For a 6-person team without dedicated DevOps engineers, deploying and monitoring a single codebase minimizes operational complexity and deployment pipelines.
  3. Transaction Simplicity: Cross-domain transactions (e.g. Invoicing deducting Inventory) can execute within local database ACID transactions, avoiding distributed transactions (Sagas/2PC).
  4. Future Migration Path: Clear module boundaries allow extracting high-load components (e.g. Reporting or Invoicing) into independent microservices later if scaling demands dictate.

Why Alternatives are Less Suitable:

  • Layered Monolith (Horizontal Layers): Tend to organize code strictly by technical concerns (UI, Business, Data) across all domains, leading to high coupling between domains and difficulty in maintaining domain boundaries.
  • Microservices: Introduce excessive network latency, distributed failure modes, complex CI/CD orchestration, and distributed data consistency challenges, which would overwhelm a 6-person startup team.

Question 18: Mermaid Sequence Diagram Solution (12 marks)