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
| Question | Answer | Key Rationale |
|---|---|---|
| Question 1 | B | Aggregation: Songs have independent lifecycles from playlists; deleting a playlist does not destroy the songs in the catalogue. |
| Question 2 | B | PECS / Covariance: List<? extends Animal> allows covariance reading (List<Dog> is valid because Dog extends Animal). |
| Question 3 | B | Exceptions: Checked exceptions (subclasses of Exception excluding RuntimeException) must be caught or declared with throws. |
| Question 4 | C | Functional Interface: Predicate<T> accepts an argument of type T and returns a boolean value (Order -> boolean). |
| Question 5 | D | DIP: High-level module CheckoutService directly depends on low-level concrete implementations rather than abstractions/interfaces. |
| Question 6 | B | Observer Pattern: Defines a one-to-many dependency so that when one subject changes state, all its registered observers are notified dynamically. |
| Question 7 | C | Serverless: Ideal for event-driven, irregular, stateless compute tasks (image resizing) without provisioning persistent server infrastructure. |
| Question 8 | B | Microservices 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:
-
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 toamount >= 100. A client expecting anAccountcould pass$50and have their expectation broken byPremiumAccount, violating substitutability.
-
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 leastamount(allowing arbitrary extra fees or over-deductions), which weakens the contract guarantee.
Question 10: Java Generics Invariance (4 marks)
Answer:
-
Why it fails to compile (2 marks):
- Java generic types are invariant. Even though
Dogis a subtype ofAnimal,List<Dog>is not a subtype ofList<Animal>. - If Java allowed
List<Animal> animals = dogs;, one could subsequently executeanimals.add(new Cat());, which would insert aCatinto what is actually aList<Dog>, compromising runtime type safety (Heap Pollution).
- Java generic types are invariant. Even though
-
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()returnsAnimal). The compiler safely allows passingList<Dog>,List<Cat>, orList<Animal>, while preventing invalid element insertions.
- Use an upper-bounded wildcard:
Question 11: Code & Design Smells in ReportExporter (5 marks)
Answer:
-
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 additionalelse ifbranches, risking regression in existing formats. - Refactoring: Apply the Strategy Pattern or Polymorphic Exporters. Create an interface
ReportFormatStrategywith a methodString format(Report report), and instantiate specific implementations (CsvReportStrategy,JsonReportStrategy,XmlReportStrategy).
- Problem: Adding a new format (e.g., YAML, PDF) requires modifying
-
Smell 2: Long Method / Lack of Cohesion / Mixed Concerns (2.5 marks)
- Problem:
ReportExportermixes 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.
- Problem:
Question 12: Design Pattern - Delivery Add-ons (4 marks)
Answer:
- Chosen Pattern: Decorator Pattern (Structural).
- Mapping & Justification:
- Component Interface:
DeliveryServicedefiningdouble calculateCost()andvoid executeDelivery(). - Concrete Component:
StandardDeliveryproviding base delivery behavior and pricing. - Base Decorator:
DeliveryOptionDecoratorimplementingDeliveryServiceand wrapping aDeliveryServicereference. - 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.
- Component Interface:
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()andvoid display(int indentLevel). - Leaf:
Lessonrepresenting individual terminal units of learning content without children.getEstimatedStudyTime()returns its own duration. - Composite:
Modulecontaining a collection of childCourseIteminstances (which can beLessons or nestedModules).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.
- Component Abstraction:
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:
- 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.
- 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:
- 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.
- 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.
- Transaction Simplicity: Cross-domain transactions (e.g. Invoicing deducting Inventory) can execute within local database ACID transactions, avoiding distributed transactions (Sagas/2PC).
- 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.