STUDIO 17 · MOCK EXAMS

Mock Exams — the Real Papers, Self-Marked

The three real assessments for this module — the two in-class assignments and the 2025 final paper — reproduced faithfully, question by question, mark allocation by mark allocation. There are no hint ladders here: a mock is not a tutorial. You sit the paper cold, on the clock, and only then open the model answers to mark yourself against a real rubric. Every model answer is written the way a top student would argue it — naming the principle is worth almost nothing; the marks live in the justification.

How to use this studio

This page only works if you use it in the right order. The temptation is to peek. Don't.

  1. Sit each paper TIMED, on paper or in a plain editor, with every reveal closed. Give the assignments and the final paper their full 2 hours each. Answer all parts as if it were the hall — no chapter tabs open, no searching, no model answers.
  2. Only then come back here. Transcribe what you actually wrote into the commit box under each sub-part (your text is saved locally in your browser, so you can close the tab and return).
  3. Self-mark against the rubric. Each model answer carries the real mark breakdown — award yourself the sub-marks honestly, line by line, then read the "common mistake" note to see if you fell into it.
  4. Send every dropped mark to a chapter. The model answers cross-link the exact section that fixes the weakness. The How you did section at the end turns your scores into a study plan.
⚠️ Reading model answers first destroys the learning

The single most valuable thing on this page is the gap between what you wrote under pressure and the full-marks answer. If you read the model first, that gap vanishes and so does the learning — you'll recognise the right answer without being able to produce it in the exam. Recognition is not recall. Commit your own answer, in full, before you open a single reveal. Treat the closed details boxes as sealed until the clock stops.

ℹ️ How the marking rubrics read

Each rubric mimics a marking scheme: ✔ Full credit is what earns the whole allocation, ◐ Partial is what a half-answer scores, and ✘ Common mistake is the classic error that caps you. Where the exam asks you to compare two things (Factory Method vs Abstract Factory, SOA vs MSA, RMI vs CORBA, MVC vs 3-tier), the model answer is a table — because that is exactly how an examiner wants the difference laid out.

Assignment 1 — Parking Lot Management System

In-class · 2 hours · 100 marks. You design the business object model for a multi-level parking facility: vehicles enter and exit, spots of different sizes are allocated, fees are calculated. Part 1 (50 marks) tests OO fundamentals and object relationships; Part 2 (50 marks) tests the five SOLID principles. Assume the UI and persistence are separate concerns — model the business logic only.

Part 1 — OO Design Fundamentals & Object Relationships (50 marks)

Q1 Abstraction example [5 marks]

Describe an example of an object relationship you would propose, based on the Parking Lot Management System, to model the object-oriented principle of Abstraction.

Your answer is saved locally in your browser.
Model answer & rubric

Abstraction is about exposing what an object does while hiding how it does it. The cleanest example in this domain is an abstract Vehicle (or an IParkable interface) that declares the essential behaviour every vehicle must offer — GetSize(), CalculateFee(Duration d) — without committing to any one concrete vehicle. Car, Motorcycle and Bus are concrete realisations that fill in the detail.

The relationship that carries the abstraction is the dependency of ParkingLot on the Vehicle abstraction: ParkingLot.ParkVehicle(Vehicle v) is written entirely against the abstract type. The lot "knows" only the contract — that any vehicle can report its required spot size — and is deliberately ignorant of engines, wheel counts or passenger capacity. That ignorance is the abstraction: it lets the lot reason about a bus and a motorcycle through one simplified concept. A future ElectricVehicle slots in without the lot changing, which is the payoff of abstracting at the right seam. See the four pillars and object relationships.

Marking rubric
  • ✔ Full credit (5): names a concrete abstraction from the domain (abstract Vehicle / IParkable) and a relationship that uses it (ParkingLot depends on the abstract type) and states clearly what implementation detail is hidden and why that helps.
  • ◐ Partial (2–3): defines abstraction correctly but the example is generic or the "what is hidden" argument is missing.
  • ✘ Common mistake: confusing abstraction with encapsulation — writing about private fields and getters instead of an abstract type that clients depend on. Encapsulation hides data; abstraction hides a whole implementation behind a simplified contract.
Q2·A Three private attributes of ParkingSpot [5 marks]

ParkingSpot should be encapsulated. Propose 3 attributes you would make private within the class, and explain the reason for each selection.

Your answer is saved locally in your browser.
Model answer & rubric

Encapsulation means the field is private because there is a rule about it that outsiders must not be able to break. Three good choices, each with its invariant:

  • private bool isOccupied — occupancy must only ever flip through Assign() / Release(). If it were public, two vehicles could be parked in one spot by careless code setting the flag directly; the class could no longer guarantee its own consistency.
  • private SpotSize size — the size is fixed at construction and determines which vehicles fit. Making it private and read-only stops anyone mutating a "large" bus bay into a "small" one at runtime, which would corrupt allocation logic.
  • private Vehicle? occupant — the spot must reference at most one vehicle, and only while occupied. Hiding it prevents external code from pointing a spot at a vehicle that never parked there, keeping isOccupied and occupant in agreement.

The theme in every justification is the same: private + methods = the class enforces its invariants instead of trusting every caller. See encapsulation.

Marking rubric
  • ✔ Full credit (5): three sensible private fields, each with a reason tied to protecting internal state / an invariant — not just "it's good practice".
  • ◐ Partial (2–3): three fields but reasons are generic ("hide data") or one field is really behaviour.
  • ✘ Common mistake: listing fields with no invariant — the marks are in why uncontrolled external modification would be harmful, not in the list itself.
Q2·B Two public functions of ParkingSpot [5 marks]

Propose 2 public functions you would expose on ParkingSpot to let it interact with external objects, and explain how these functions protect the internal state from direct, uncontrolled modification.

Your answer is saved locally in your browser.
Model answer & rubric

The two functions should be the only doors into the private state, and each should validate before it mutates:

  • public bool Assign(Vehicle v) — checks the invariant first: if the spot is already occupied, or v.RequiredSize > this.size, it refuses and returns false; otherwise it sets occupant = v and isOccupied = true together. External code can never leave the two fields inconsistent because it cannot set either directly.
  • public void Release() — clears occupant = null and isOccupied = false as one atomic operation, so the spot cannot end up "free but still pointing at a car". A caller asks the spot to release; it does not reach in and null a field.

Protection comes from the fact that both methods are gatekeepers: they run validation and keep related fields in step, so the object's rules hold no matter who calls. This is the behavioural half of encapsulation — private data plus a controlled public API.

Marking rubric
  • ✔ Full credit (5): two methods that clearly mediate access, with the validation/guard described and an explicit statement that they keep private fields consistent.
  • ◐ Partial (2–3): two plausible methods but the "how it protects state" is asserted, not shown (no validation or consistency argument).
  • ✘ Common mistake: proposing naked getters/setters — a public set that assigns the field with no check is not protection; it's the exposed field with extra steps.
Q3·A Unique subclass attributes [4 marks]

Assume Car, Motorcycle, Bus inherit from Vehicle. What specific attributes (3) would each subclass uniquely define (not defined in Vehicle)? State any assumptions.

Your answer is saved locally in your browser.
Model answer & rubric

The discipline here: an attribute belongs on the subclass only if it makes no sense on every vehicle. Assumption stated up front: Vehicle already holds the common data (see Q3·B), so these are the genuinely type-specific extras.

  • Car: numberOfDoors, hasChildSeat, bootCapacityLitres.
  • Motorcycle: hasSidecar, engineCc, requiresHelmetStorage.
  • Bus: passengerCapacity, numberOfAxles, isDoubleDecker.

Each of these would be meaningless or wasteful if pushed up to Vehicle (a motorcycle has no boot; a car has no axle count worth modelling), which is precisely the test for "belongs on the subtype". See inheritance and where inheritance is appropriate.

Marking rubric
  • ✔ Full credit (4): three plausible, genuinely subtype-specific attributes per class, with an assumption stated. Roughly 1 mark of the 4 rides on the attributes being truly specific (not common data in disguise).
  • ◐ Partial (2): attributes given but several are actually shared (e.g. putting colour or licensePlate on a subclass).
  • ✘ Common mistake: duplicating a field that should live once on Vehicle — that defeats the point of the superclass.
Q3·B Shared superclass attributes [4 marks]

Give 2 attributes that would be shared among the superclass Vehicle and its subclasses.

Your answer is saved locally in your browser.
Model answer & rubric

A field belongs on Vehicle when every subtype needs it and none would define it differently. Two clear cases:

  • licensePlate (string) — every vehicle entering the lot is identified this way; the ticket and exit lookup both rely on it.
  • requiredSpotSize (enum Small/Medium/Large) — every vehicle reports the size of spot it needs. Note this is declared on the base but its value is set per subtype, which is what makes the polymorphic allocation in Q3·D work.

entryTime is an equally valid answer. The reasoning that earns the marks: placing shared state once on the base avoids duplication and lets ParkingLot treat all vehicles uniformly. See inheritance and shared state.

Marking rubric
  • ✔ Full credit (4): two genuinely universal fields with a one-line justification that they are common to all vehicles.
  • ◐ Partial (2): fields named but one is really subtype-specific, or no justification.
  • ✘ Common mistake: choosing something a subtype would override in meaning (e.g. a fee rate, which differs by type) rather than shared data.
Q3·C Overridden functions [4 marks]

Provide 2 functions of the superclass Vehicle that would be overridden by the subclasses.

Your answer is saved locally in your browser.
Model answer & rubric

A method is worth overriding only when the subtypes genuinely do it differently:

  • CalculateFee(TimeSpan duration) — a bus is charged at a higher hourly rate than a car, a motorcycle lower. Same signature, different arithmetic per subtype. This is the override that Q3·E asks you to exploit.
  • GetRequiredSpotSize() — returns Large for Bus, Medium for Car, Small for Motorcycle. The lot calls it without knowing the concrete type.

Both are declared virtual/abstract on Vehicle and override in each subclass, so a Vehicle reference dispatches to the right body at run time. See polymorphism.

Marking rubric
  • ✔ Full credit (4): two methods whose behaviour genuinely varies by subtype, with the difference named.
  • ◐ Partial (2): methods given but they would realistically be identical across subtypes (nothing to override).
  • ✘ Common mistake: proposing a getter for a field as an "override" — overriding is about differing behaviour, not exposing differing data.
Q3·D Polymorphism in parkVehicle() [4 marks]

Explain how polymorphism lets parkVehicle(Vehicle vehicle) handle Car, Motorcycle and Bus differently without explicit type checking (no if (vehicle instanceof Car)).

Your answer is saved locally in your browser.
Model answer & rubric

The method parameter is typed as the abstraction, Vehicle, so at compile time parkVehicle knows only the contract. When it calls vehicle.GetRequiredSpotSize(), the runtime looks at the actual object — a Bus, say — and dispatches to Bus.GetRequiredSpotSize(). This is dynamic dispatch (late binding): the object carries its own behaviour, so the method behaves differently for each subtype while its code is written once.

That is exactly why no instanceof ladder is needed. A switch on type would force parkVehicle to be modified every time a new vehicle is added (an OCP violation); polymorphism pushes the per-type decision into the subclasses, so adding ElectricVehicle means adding a class, not editing the lot. The decision lives with the data that knows the answer. See polymorphism and OCP.

Marking rubric
  • ✔ Full credit (4): 2 for naming dynamic dispatch / the single Vehicle-typed reference; 2 for explaining that overridden methods let one code path branch by object, so the type-check disappears and the design stays open to new types.
  • ◐ Partial (2): says "polymorphism handles it" but doesn't explain late binding or link it to avoiding the switch.
  • ✘ Common mistake: describing overloading (many methods, chosen at compile time) instead of overriding (one method, chosen at run time). Overloading would reintroduce the type dependency.
Q3·E Polymorphism in fee calculation [4 marks]

Describe how the system would leverage polymorphism when calculating the parking fee, assuming fees vary by vehicle type.

Your answer is saved locally in your browser.
Model answer & rubric

At exit, the lot holds a Vehicle reference and simply calls vehicle.CalculateFee(duration). Because CalculateFee is overridden per subtype, a Bus object runs the bus rate, a Car the car rate — the billing code never asks "what kind of vehicle is this?" It relies on each object knowing its own pricing. Adding ElectricVehicle with a discounted rate requires no change to the exit/billing path at all.

A stronger answer notes the design tension: baking the fee into the vehicle subtype couples pricing policy to the vehicle hierarchy, so introducing surge pricing or long-term discounts (Part 2, Q2) would multiply subclasses. The more flexible variant is to give Vehicle an injected IFeeStrategy and delegate: polymorphism then varies the strategy, not the vehicle class. Either answer earns full marks if the dispatch argument is clear; the Strategy remark shows top-band judgement. See Strategy and OCP.

Marking rubric
  • ✔ Full credit (4): explains that the lot calls an overridden CalculateFee on a base-typed reference and the correct per-type calculation runs via dynamic dispatch, with no type checks.
  • ◐ Partial (2): mentions polymorphism but ties it to fields rather than the overridden fee method.
  • ✘ Common mistake: proposing a switch(vehicle.Type) inside a central FeeCalculator — that is the anti-pattern polymorphism is meant to remove.
Q3·F Relationships among ParkingLot, ParkingSpot, Vehicle [10 marks]

Describe the relationships — ParkingLotParkingSpot, ParkingLotVehicle, ParkingSpotVehicle — using Association, Aggregation and Composition.

Your answer is saved locally in your browser.
Model answer & rubric

The deciding test throughout is lifetime ownership, not merely "does A hold a reference to B".

  • ParkingLot — ParkingSpot: Composition. The spots are an intrinsic, permanent part of the lot; they are created with it and have no meaning outside it. If the lot is demolished, its spots cease to exist. Strong ownership + shared lifetime = composition (filled diamond at the ParkingLot end, multiplicity 1..*).
  • ParkingSpot — Vehicle: Association (temporary). While occupied, a spot references the vehicle in it, but neither owns the other and the link is transient — it exists only for the duration of the stay, then breaks on exit. This is a plain association (a "uses/occupies" link), 0..1 on each side. It is not composition — the vehicle lives on after leaving — but note that alone wouldn't rule out aggregation too (aggregation is independent lifetime by definition); the real reason it isn't aggregation either is that a spot doesn't have a vehicle as a part, it just temporarily references one — there's no whole-part semantics here at all.
  • ParkingLot — Vehicle: Association. The lot deals with vehicles that pass through it; a vehicle exists entirely independently of the lot and is shared with the outside world. A transient "manages/parks" association, not aggregation of owned parts — the lot never owns the vehicles.

(If you argued ParkingLotVehicle as aggregation on the grounds that the lot holds a collection of currently-parked vehicles, that is defensible and earns the marks provided you make the independent-lifetime argument — the examiner rewards the justification, not the label.) See association / aggregation / composition.

Marking rubric
  • ✔ Full credit (10): ~3 marks per pair (correct relationship + lifetime/ownership justification), ~1 mark for using the deciding test consistently. The justification is the bulk of the marks.
  • ◐ Partial (5–6): correct labels but justified by "has-a" alone, with no lifetime argument.
  • ✘ Common mistake: calling ParkingSpotVehicle composition or aggregation and justifying it purely by "the vehicle outlives the parking event, so it isn't owned" — independent lifetime is exactly what aggregation means, so that argument alone doesn't rule it out. The real reason it's plain association is the lack of whole-part semantics: a spot doesn't have a vehicle as a part, it temporarily references one. Swapping composition and aggregation for the lot–spot link (spots don't outlive the lot) is the other classic slip.
Q3·G UML class diagram of the relationships [5 marks]

Draw a UML class diagram representing the relationships identified in Q3·F. Sketch it on paper first — decide diamond ends and multiplicities before you look.

Model diagram & common drawing mistakes ParkingLot ParkingSpot Vehicle «abstract» Car Motorcycle Bus composition 1..* 1 parks ▸ (association) occupies ▸ (association) 0..1

What a full-marks diagram shows: a filled diamond at the ParkingLot end of the lot–spot line (composition) with 1..* multiplicity; plain association lines (no diamond) for both links to Vehicle, the spot–vehicle one labelled 0..1; and hollow-triangle inheritance arrows from Car/Motorcycle/Bus up to the abstract Vehicle.

Common drawing mistakes
  • ✘ Putting the composition diamond at the spot end instead of the owning ParkingLot end — the diamond always sits with the whole/owner.
  • ✘ Drawing the spot–vehicle or lot–vehicle links with a diamond (implying ownership) instead of a plain association line.
  • ✘ Using a filled/solid arrowhead for inheritance — inheritance is a hollow triangle.
  • ✘ Omitting multiplicities entirely (the 1..* and 0..1 carry marks).
Marking rubric
  • ✔ Full credit (5): correct relationship notation for all three pairs (2), correct diamond placement and multiplicities (2), inheritance arrows drawn correctly (1).
  • ◐ Partial (2–3): right classes and links but wrong notation (diamond ends, arrowheads) or missing multiplicities.

Part 2 — SOLID Design Principles (50 marks)

Q1·A Extract responsibilities (SRP) [6 marks]

A monolithic ParkingLot handles parking/unparking, fee calculation, report generation and payment. Identify at least 2 distinct responsibilities that could be extracted to adhere to the Single Responsibility Principle.

Your answer is saved locally in your browser.
Model answer & rubric

The SRP test is "one reason to change". ParkingLot currently has four:

  • Fee calculation — changes when pricing policy changes (surge, discounts). A distinct reason to change.
  • Report generation / formatting — changes when the report layout or output format changes. Nothing to do with parking rules.
  • Payment processing — changes when a payment provider or gateway changes.

Each of these is an independent axis of change entangled in one class today, which is why a change to the report format risks breaking parking logic. The core responsibility that stays in ParkingLot is allocation/occupancy (parking and unparking). See SRP.

Marking rubric
  • ✔ Full credit (6): two (or more) genuinely distinct responsibilities, each articulated as a separate reason to change — 3 marks each.
  • ◐ Partial (3): two responsibilities named but justified vaguely ("it does too much") rather than as independent change axes.
  • ✘ Common mistake: listing two things that are really one responsibility (e.g. "parking" and "unparking") — those change for the same reason.
Q1·B Propose the extracted classes [6 marks]

For each responsibility identified in Q1·A, propose a new class that encapsulates it, and explain how this improves the design.

Your answer is saved locally in your browser.
Model answer & rubric
  • FeeCalculator (behind an IFeeCalculator) — owns pricing. Now a new fee rule is a change in one small class the parking logic doesn't touch, and the calculator can be unit-tested in isolation with no lot involved.
  • ReportGenerator — owns formatting/output of reports. Changing to PDF or a new layout affects only this class; it can be reused by other subsystems that need the same reports.
  • PaymentProcessor (behind an IPaymentProcessor) — owns the payment gateway interaction, so swapping providers or mocking payments in tests never disturbs allocation.

ParkingLot retains only allocation and collaborates with these via interfaces. The improvement to state explicitly: each class now has one reason to change, so changes are localised, the pieces are independently testable, and they become reusable. This is SRP feeding straight into DIP (the lot depends on abstractions).

Marking rubric
  • ✔ Full credit (6): one well-named class per responsibility (3) and a concrete design benefit — localised change, testability, or reuse — not just "cleaner" (3).
  • ◐ Partial (3): classes proposed but the benefit is generic.
  • ✘ Common mistake: extracting the class but leaving the logic duplicated in ParkingLot, or having the lot still new the concrete class (misses the DIP payoff).
Q2 Open for extension, closed for modification (OCP) [12 marks]

The lot must support new vehicle types (Electric, Bicycle) and new fee structures (day pricing, surge, long-term discounts). Explain, with examples, how you would design the Vehicle and fee-calculation hierarchies to be open for extension but closed for modification. Name the primary SOLID principle complied with.

Your answer is saved locally in your browser.
Model answer & rubric

Principle: the Open/Closed Principle (OCP) — software entities should be open for extension but closed for modification.

Vehicle hierarchy. Keep Vehicle abstract with the stable contract (GetRequiredSpotSize(), and a fee hook). A new type — ElectricVehicle, Bicycle — is a new subclass that supplies its own behaviour. Crucially, ParkingLot.ParkVehicle(Vehicle v) is written against the abstraction, so it is never edited when a type is added: the new class plugs in and dynamic dispatch does the rest. That is "closed to modification" — the core allocation logic is sealed against change — while remaining "open to extension" through subclassing.

Fee structure. Do not fold pricing into the vehicle subtypes (that would explode into SurgeCar, LongTermBus…). Instead extract an IFeeStrategy with decimal Calculate(TimeSpan duration, Vehicle v), and implement HourlyFeeStrategy, DayPricingStrategy, SurgeFeeStrategy, LongTermDiscountStrategy. A new rule is a new strategy class; the parking and billing code that holds an IFeeStrategy reference never changes. This is OCP realised through Strategy and injected via DIP.

The unifying argument: both hierarchies push the point of variation behind an abstraction, so extension means adding a class and the existing, tested core logic is untouched — fewer regressions, safer growth. See OCP.

Marking rubric
  • ✔ Full credit (12): ~2 for naming OCP correctly; ~4 for the vehicle extension mechanism (abstract base + new subclass, lot coded to the abstraction); ~4 for the fee mechanism (Strategy/interface, new rule = new class); ~2 for explaining why core logic stays closed (coded against abstractions, no edits to tested code, no combinatorial explosion).
  • ◐ Partial (5–7): names OCP and handles one hierarchy well but leaves the other as a switch or type-check.
  • ✘ Common mistake: proposing if/else or switch(vehicleType) in a central calculator — that is the exact "modify existing code" that OCP forbids. Also: baking every fee rule into vehicle subclasses, which multiplies the hierarchy instead of composing it.
Q3 Liskov Substitution in the lot [12 marks]

With Car and Motorcycle inheriting Vehicle, describe a scenario where violating the Liskov Substitution Principle causes unexpected behaviour in the parking system. Explain the guidelines you would follow so Car and Motorcycle can always substitute a Vehicle reference without breaking functionality.

Your answer is saved locally in your browser.
Model answer & rubric

A concrete violation. Suppose Vehicle declares virtual decimal CalculateFee(TimeSpan d) with the implied contract "returns a non-negative fee for any positive duration". Now Motorcycle overrides it to throw for stays over 24 hours ("bikes can't stay overnight"). The billing loop iterates a List<Vehicle> and calls CalculateFee on each; when it reaches a long-staying motorcycle it crashes, even though the code was correct against the Vehicle contract. The subtype broke a promise the supertype made — a textbook LSP failure. Another example: a Bicycle subclass whose GetRequiredSpotSize() returns null/none, so ParkVehicle — which assumes every vehicle needs some spot — dereferences it and fails.

Guidelines to preserve substitutability:

  • Don't strengthen preconditions. A subclass must accept every input the base accepts (a motorcycle can't reject durations the base allows).
  • Don't weaken postconditions. It must still deliver everything the base promised (return a valid fee, a valid spot size) — no surprising nulls.
  • Don't throw new exceptions the base contract didn't sanction, and don't silently do nothing where the base does something.
  • Honour invariants of the base type; keep the behaviour a caller would reasonably expect.
  • If a would-be subtype genuinely can't fulfil the contract, it isn't a subtype — model the capability separately (composition / a narrower interface) rather than forcing the inheritance.

Follow these and any Vehicle reference can hold a Car or Motorcycle interchangeably, which is what lets the lot's polymorphic code (Q3·D/E) stay correct. See LSP.

Marking rubric
  • ✔ Full credit (12): ~5 for a concrete, in-domain violation that shows how substituting the subtype breaks working code; ~5 for the substitutability guidelines (preconditions, postconditions, no new throws, invariants); ~2 for the "if it can't honour the contract, don't inherit" judgement.
  • ◐ Partial (6): defines LSP and gives a vague example, but no contract rules or an example that isn't really a substitution failure.
  • ✘ Common mistake: describing an override that merely differs (e.g. a different fee rate) as an LSP violation — differing behaviour is fine; breaking the promised contract is the violation.
Q4 Interface Segregation for external components [14 marks]

The ParkingLot interacts with PaymentGateway, SecurityCameraSystem and DisplayBoard. Instead of one monolithic ParkingLotInterface, describe how you would apply the Interface Segregation Principle to design granular interfaces, and explain a benefit over the single interface.

Your answer is saved locally in your browser.
Model answer & rubric

The problem with the monolith. A single ParkingLotInterface bundling ProcessPayment(), StreamCamera(), UpdateDisplay(), RecordEntry()… forces every client to depend on methods it doesn't use. The DisplayBoard must "implement" payment methods (throwing or leaving them empty), and — worse — any change to the payment part of the fat interface forces the display and camera implementers to recompile and be re-tested even though nothing about them changed. ISP says: no client should be forced to depend on methods it does not use.

The segregated design — small, role-focused (client-specific) interfaces:

  • IPaymentProcessor { ProcessPayment(); Refund(); } — implemented by PaymentGateway.
  • ISurveillance { StartRecording(); CaptureSnapshot(); } — implemented by SecurityCameraSystem.
  • IDisplay { ShowAvailability(); ShowMessage(); } — implemented by DisplayBoard.

Each component implements only the interface for its role; ParkingLot depends on the specific abstractions it actually needs for a given collaboration.

Benefits (state at least one clearly): (1) implementers are no longer burdened with irrelevant methods or forced to write empty/throwing stubs; (2) changes are isolated — altering IPaymentProcessor doesn't ripple into the camera or display code, so there's less coupling and fewer needless recompilations/re-tests; (3) each interface is easier to mock and test in isolation. See ISP.

Marking rubric
  • ✔ Full credit (14): ~4 for articulating the fat-interface problem (clients forced to depend on unused methods); ~6 for a clean split into role interfaces with the right implementer for each; ~4 for a concrete benefit argued (decoupling / no empty stubs / isolated change / testability).
  • ◐ Partial (7): splits the interface but the mapping is muddled, or states "it's better" without a specific benefit.
  • ✘ Common mistake: splitting by class rather than by role/client need, or producing one interface per method (over-segregation) instead of grouping cohesive operations.

Assignment 2 — Design Patterns

In-class · 2 hours · answer all questions. This paper is pure GoF pattern judgement: name the category difference, distinguish look-alike patterns, and map patterns onto concrete scenarios (a configurable computer builder, a legacy financial system, heterogeneous product catalogs). The high-mark questions want a pattern and a UML sketch and a "why this over that" argument.

Q1 The three pattern categories [10 marks]

Software design patterns are broadly categorised into three fundamental types. Identify these three categories and clearly explain the main conceptual difference that distinguishes each from the others.

Your answer is saved locally in your browser.
Model answer & rubric

The three categories are organised by what aspect of a design the pattern is concerned with:

  • Creational — object creation. These abstract the instantiation process, so client code doesn't hard-wire concrete classes or complex construction. They answer "how and when is an object made, and by whom?" Examples: Factory Method, Builder, Singleton.
  • Structural — object composition. These concern how classes and objects are assembled into larger structures while keeping them flexible — the shape of the relationships. They answer "how are objects composed/related?" Examples: Adapter, Composite, Decorator.
  • Behavioural — object interaction and responsibility. These concern how objects communicate and distribute responsibility at run time — the flow of control and messages. They answer "how do objects collaborate and who does what?" Examples: Observer, Strategy, Template Method.

The clean one-line contrast that earns the marks: creational is about making objects, structural about arranging them, behavioural about how they interact.

Marking rubric
  • ✔ Full credit (10): all three named (roughly 2 each = 6) plus a genuine conceptual distinction for each — creation vs composition vs interaction (4). An example per category strengthens but isn't required.
  • ◐ Partial (5–6): three categories named but "explained" only by listing patterns, with no conceptual difference stated.
  • ✘ Common mistake: defining each category by its patterns rather than by what the category is about — the question explicitly asks for the conceptual difference.
Q2 Factory Method vs Abstract Factory [10 marks]

Both belong to the creational category yet address different aspects of creation. Explain the fundamental difference between Factory Method and Abstract Factory. Give a clear, distinct example for each and show why one would be chosen over the other.

Your answer is saved locally in your browser.
Model answer & rubric

Fundamental difference: Factory Method defers the creation of one product to a subclass via a single overridable method; Abstract Factory provides an interface for creating a whole family of related products that are guaranteed to be consistent with one another, via several such methods on one factory object.

DimensionFactory MethodAbstract Factory
What it createsOne product, chosen by subclassA family of related products, kept consistent
MechanismInheritance — override a single creation methodObject composition — a factory object with several creation methods
StructureOne method (CreatePage())Many methods on one interface (CreateButton(), CreateCheckbox())
ExampleDocument.CreatePage(): PdfDocument makes a PdfPage, WordDocument a WordPageIGuiFactory: WinFactory makes WinButton + WinCheckbox, MacFactory makes MacButton + MacCheckbox
Choose whenSubclasses vary a single productYou must produce a matching set and mixing families is a bug

The choosing rule to state explicitly: use Factory Method when there is one product whose concrete type varies; reach for Abstract Factory when you need several related products created together and guaranteed to match (a Windows checkbox must never pair with a Mac button). Abstract Factory is often built from several factory methods. See Factory Method, Abstract Factory, and the factories comparison.

Marking rubric
  • ✔ Full credit (10): ~4 for the core "one product vs a family/set" distinction; ~4 for a distinct, correct example of each; ~2 for the "why choose one over the other" rule (consistency of a family).
  • ◐ Partial (5): distinction stated but examples are the same shape, or only one pattern is exemplified.
  • ✘ Common mistake: saying Abstract Factory is "just a bigger Factory Method" without the family consistency point, or giving a Builder-style example (assembling one object's parts) for Abstract Factory.
Q3 Customisable computer — Builder + UML [20 marks]

A company builds highly customisable computers (CPU, RAM, storage, GPU, OS, peripherals). A single constructor new Computer(cpu, ram, storage, gpu, os, monitor, keyboard, mouse) telescopes badly. Propose a suitable creational pattern to construct customised computers (Office / Gaming / Workstation), draw the class composition in UML, and describe the class relationships.

Your answer is saved locally in your browser.
Model answer, UML & common drawing mistakes

Pattern: Builder. The telling symptoms — many optional parts, a telescoping constructor, "Gaming PC vs Office PC vs Workstation" presets, and the desire to add a component without breaking every caller — are the textbook cue for Builder. It separates the construction of a complex object from its representation, so the same step-by-step process can assemble different configurations.

Roles:

  • Computer — the Product being assembled (holds cpu, ram, storage, gpu, os, peripherals).
  • IComputerBuilder — the Builder interface declaring the steps: SetCpu(), SetRam(), SetStorage(), SetGpu(), SetOs(), AddPeripherals(), Build().
  • GamingPcBuilder, OfficePcBuilder, WorkstationBuilderConcreteBuilders, each supplying the parts for that configuration.
  • ComputerDirector — the optional Director that knows the recipe (the order of steps) and drives a builder to produce a standard preset, e.g. Construct(IComputerBuilder b).

Relationships: the concrete builders realise (implement) IComputerBuilder; the Director holds an association to IComputerBuilder (it is configured with one and calls its steps); each builder creates a Computer (a dependency, drawn dashed); the Computer composes its parts.

ComputerDirector Construct(builder) «interface» IComputerBuilder SetCpu() … Build() Computer cpu, ram, gpu, storage, os … GamingPcBuilder OfficePcBuilder WorkstationBuilder uses ▸ creates ▸

A student answer typically also shows the fluent usage: new GamingPcBuilder().SetCpu("i9").SetGpu("RTX").Build(), and notes that adding a new component means adding one step to the interface/builders, not editing every caller.

Common drawing mistakes
  • ✘ Drawing concrete builders → interface with a solid inheritance triangle instead of a dashed realization triangle (it's an interface).
  • ✘ Making the Director inherit the builder rather than hold one (association).
  • ✘ Omitting the Computer product entirely, or drawing the "creates" link as a solid association instead of a dashed dependency.
  • ✘ Confusing this with Abstract Factory (families of products) — Builder assembles one complex product step by step.
Marking rubric
  • ✔ Full credit (20): ~4 name + justify Builder against the telescoping-constructor symptoms; ~6 describe the four roles (Product, Builder, ConcreteBuilders, Director) with responsibilities; ~6 a correct UML with realization + association + dependency notation; ~4 describe the relationships in words and connect to OCP (new component = new step).
  • ◐ Partial (10): names Builder and lists roles but the UML notation is wrong or relationships aren't explained.
  • ✘ Common mistake: proposing Abstract Factory or a plain Factory — they don't solve the many-optional- parts assembly problem the scenario describes.
Q4 Post-construction structural patterns [20 marks]

The already-built Computer objects now need to be represented, enhanced, or organised in a larger system. (1) Identify and describe two distinct structural patterns relevant here (hint: dynamic feature addition; hierarchical composition). (2) For each, explain how it applies to the Computer with a concrete post-assembly problem it solves. (3) Articulate the main conceptual difference between them and when you'd choose one over the other.

Your answer is saved locally in your browser.
Model answer & rubric

Pattern 1 — Decorator (dynamic feature addition). Decorator wraps an object in another object of the same interface to add behaviour at run time, stackably. Applied to Computer: after assembly you need to add optional post-build enhancements — an extended warranty, pre-installed software bundle, RGB lighting, overclock profile — in any combination. Rather than a subclass per combination (GamingPcWithWarrantyAndRgb… — a combinatorial explosion), wrap: new RgbDecorator(new WarrantyDecorator(gamingPc)). Each decorator implements IComputer, forwards to the wrapped computer, and augments the result (e.g. adjusts GetPrice()/GetDescription()). The concrete problem solved: enhancing a built machine without touching its class or exploding the hierarchy.

Pattern 2 — Composite (hierarchical composition). Composite lets clients treat individual objects and compositions of objects uniformly through one interface. Applied to Computer: a corporate order is a tree — a "server rack" contains several workstations, each of which is itself a Computer; or a Computer is composed of components that themselves contain sub-components. You want GetTotalPrice() or GetPowerDraw() to work identically whether called on a single machine or a whole rack. A ComputerBundle (composite) and a leaf Computer both implement IComputerComponent, and the composite recurses over its children. The concrete problem solved: aggregating over an arbitrarily deep part-whole structure without the client special-casing leaves vs groups.

Conceptual difference & choice:

DecoratorComposite
IntentAdd responsibilities to one object dynamicallyCompose objects into part-whole trees, treated uniformly
ShapeWraps exactly one child (a linear chain)Holds many children (a tree)
Purpose of recursionLayering behaviourRepresenting a hierarchy
Choose whenYou need to add/stack features on a finished machineYou need to manage groups of machines/parts as one

Choose Decorator when the requirement is "add optional features to an assembled computer"; choose Composite when it is "manage internal parts / groups of computers as a single unit". Both keep the same interface (unlike Adapter, which changes it). See the Composite vs Decorator comparison.

Marking rubric
  • ✔ Full credit (20): ~4 identify + describe two distinct structural patterns (Decorator + Composite are the intended pair); ~8 a concrete Computer application for each with a real post-assembly problem; ~8 the conceptual difference (one child/layering vs many children/hierarchy) and the choosing rule.
  • ◐ Partial (10): both patterns named and applied, but the difference is stated weakly or one example is generic.
  • ✘ Common mistake: picking Adapter or Facade — neither matches the hints "dynamic feature addition" and "hierarchical composition". Also: describing Decorator as wrapping many children (that's Composite) — blurring the very distinction the question tests.
Q5 Legacy financial system — Adapter + Proxy [20 marks]

A legacy reporting module consumes ILegacyDataSource.processFinancialRecordsInXml(...) and cannot be modified. A new transaction service exposes retrieveTransactionsJson(): List<Map> but is expensive and access-restricted. Problem 1 — format incompatibility (XML expected, JSON provided). Problem 2 — controlled, efficient access (lazy loading, caching, authorisation). Identify the best structural pattern for each, justify it, then articulate the fundamental difference between the two.

Your answer is saved locally in your browser.
Model answer & rubric

Problem 1 — Adapter. The legacy module and the new service have incompatible interfaces: one expects an XML string, the other returns a list of JSON maps, and the legacy side can't change. Adapter is the exact fit: write a TransactionServiceAdapter that implements ILegacyDataSource (the Target the legacy module already speaks) and internally calls retrieveTransactionsJson() on the new service (the Adaptee), translating the JSON maps into the XML string the legacy module expects. The adapter's whole job is interface translation so two things built to different contracts can work together without modifying either.

Problem 2 — Proxy. The concerns are lazy loading, caching, and authorisation on the same interface — a surrogate that controls access to the real object. Proxy covers all three of its classic variants at once: a virtual proxy defers the expensive retrieveTransactionsJson() call until data is genuinely needed; a caching layer stores the result so repeat calls in a short window don't hit the costly service again; and a protection proxy checks the caller is authenticated/authorised before forwarding. The TransactionServiceProxy implements the same interface as the real service and stands in front of it, so clients are unaware they're not talking to the real thing.

Fundamental difference:

Adapter (Problem 1)Proxy (Problem 2)
Primary goalMake two incompatible interfaces work togetherControl access to an object behind the same interface
InterfaceChanges it (XML target ← JSON adaptee)Keeps it identical to the real subject
Problem solvedInterface mismatch / integrationCost, laziness, caching, security
Client awarenessClient uses the target interface knowinglyClient thinks it's talking to the real object

In this scenario: the Adapter exists because the shapes don't match; the Proxy exists because access must be governed. Different problems, hence different patterns — even though both are "wrappers". See the wrapper-family litmus.

Marking rubric
  • ✔ Full credit (20): ~6 Adapter for P1 with the XML↔JSON translation role explained; ~8 Proxy for P2 covering lazy loading + caching + authorisation as proxy variants; ~6 the fundamental difference (interface translation vs access control; changes interface vs keeps it).
  • ◐ Partial (10): both patterns named correctly but one justification is thin (e.g. Proxy stated without tying to all three concerns).
  • ✘ Common mistake: calling Problem 2 a Decorator — Decorator adds features; here the wrapper's intent is controlling access (lazy/secure/cached), which is Proxy. Or using a Facade for Problem 1 — a Facade simplifies many subsystems; it doesn't translate one incompatible interface.
Q6 Diverse product catalogs — Iterator [20 marks]

Suppliers store products in different internal structures (array, linked list, tree). The Product Display Module must traverse, filter and total inventory across any catalog without coupling to the internal representation. (1) Identify the most appropriate behavioural pattern. (2) Explain its core purpose and how it decouples the client. (3) Map its components to the scenario. (4) Articulate the benefits.

Your answer is saved locally in your browser.
Model answer & rubric

Pattern: Iterator. The problem is "traverse heterogeneous collections uniformly without exposing their internal structure" — the definition of Iterator.

Core purpose & decoupling: Iterator provides a way to access the elements of an aggregate sequentially without exposing its underlying representation. Each supplier's catalog exposes a standard GetIterator() returning an IIterator<Product> with HasNext() / Next(). The Product Display Module programs against that interface only, so it walks an array-backed, a linked-list-backed, and a tree-backed catalog with identical code. The traversal logic lives inside each concrete iterator, not in the client, which is what breaks the coupling: when a supplier changes its internal structure, only that supplier's iterator changes; the display module is untouched (Open/Closed).

Component mapping:

Iterator roleScenario element
Iterator (interface)IProductIterator with HasNext()/Next()
ConcreteIteratorArrayIterator, LinkedListIterator, TreeIterator — one per supplier structure
Aggregate (interface)ICatalog with CreateIterator()
ConcreteAggregateElectronics (array), Boutique (linked list), Crafts (tree) catalogs
ClientThe Product Display Module (traverse / filter / total)

Benefits: (1) the display module is decoupled from every internal structure — one traversal code path for all suppliers; (2) adding a new supplier with a novel structure means adding a concrete iterator, with zero change to the display module (extensibility / OCP); (3) traversal, filtering and totalling are written once; (4) multiple simultaneous traversals are possible since each iterator holds its own position. See Iterator.

Marking rubric
  • ✔ Full credit (20): ~4 identify Iterator; ~6 core purpose + the decoupling argument (traversal hidden behind a uniform interface); ~5 correct component mapping to the scenario; ~5 the benefits, especially future extensibility / OCP.
  • ◐ Partial (10): Iterator named and purpose stated, but mapping or benefits are thin.
  • ✘ Common mistake: proposing Strategy or Visitor — those vary an algorithm or add operations; the requirement is uniform traversal across differing structures, which is Iterator. Exposing the underlying collection (e.g. returning the raw array) defeats the whole point.

Final Exam 2025

Second Year, Semester I · 2 hours · answer ALL four questions. The paper spans the whole module: SOLID and OO relationships on an Uber-Eats-style order system (Q1), the three pattern families on a library system (Q2), composition-over-inheritance plus clean code and TDD (Q3), and distributed/enterprise architecture — SOA vs MSA, MVC vs 3-tier, RMI vs CORBA, MSA communication styles (Q4).

Question 1 — Online Order Management System (Uber Eats-style)

Q1·A(i) OCP on the payment hierarchy [6 marks]

Payment methods (CreditCardPayment, CashOnDelivery, DigitalWalletPayment) share a processPayment() behaviour with unique implementations. Explain how the Open/Closed Principle lets new payment types (e.g. CryptocurrencyPayment) be added without modifying existing code that processes payments.

Your answer is saved locally in your browser.
Model answer & rubric

Define an abstraction — abstract class Payment or interface IPayment — declaring processPayment(). Each method is a subclass/implementer overriding processPayment() with its own logic. The order-processing code holds the abstraction: void Checkout(IPayment payment) => payment.processPayment(); — it depends only on the contract, never on a concrete type.

Because of that, adding CryptocurrencyPayment means writing one new class that implements IPayment; the checkout code, being coded against the interface and dispatching polymorphically, is not touched, recompiled logic-wise, or re-tested. The module is open to extension (new payment classes) and closed to modification (existing, tested payment-processing code stays sealed). Contrast the anti-pattern: a switch(payment.Type){ case Crypto: … } would force an edit to working code for every new method — precisely what OCP prevents. See OCP and polymorphism.

Marking rubric
  • ✔ Full credit (6): 2 for naming OCP and the abstract IPayment/processPayment() contract; 2 for the extension mechanism (new type = new subclass, selected polymorphically); 2 for why core logic stays closed (checkout coded against the abstraction, no edits to tested code).
  • ◐ Partial (3): names OCP and the hierarchy but doesn't explain how the processing code avoids modification.
  • ✘ Common mistake: describing inheritance for its own sake without the "closed to modification" half — or proposing a type switch, which violates the very principle asked about.
Q1·A(ii) DIP in the payment design [4 marks]

Describe how the Dependency Inversion Principle would guide your design so a high-level module depends on an abstraction, not a specific concrete class.

Your answer is saved locally in your browser.
Model answer & rubric

DIP has two clauses: high-level modules should not depend on low-level modules — both should depend on abstractions; and abstractions should not depend on details — details depend on abstractions. Here the high-level module is the order/checkout service; the low-level modules are the concrete payment classes. Left alone, checkout would new CreditCardPayment() and be welded to it.

DIP inverts that: introduce IPayment as the abstraction that both sides depend on. Checkout depends on IPayment; CreditCardPayment also depends on (implements) IPayment. The concrete payment is then injected into checkout (constructor or method parameter) rather than constructed inside it. Now the direction of the source-code dependency points towards the abstraction, not towards a detail: checkout can be tested with a fake IPayment, and swapping providers never edits the high-level policy. This is exactly what a DI container's constructor injection provides. See DIP.

Marking rubric
  • ✔ Full credit (4): 2 for stating both modules depend on the IPayment abstraction (the inversion); 2 for the mechanism — inject the concrete payment rather than instantiate it — and a benefit (testability / swappability).
  • ◐ Partial (2): mentions "depend on an interface" but not injection or the inversion of direction.
  • ✘ Common mistake: conflating DIP with DI — DI (injection) is the technique; DIP is the principle that the dependency should point at an abstraction. Full marks name the principle and use the technique.
Q1·B Identify the three relationships [6 marks]

Identify (Association, Composition or Aggregation) and justify each pair: (i) Restaurant and its MenuItems; (ii) Order and the MenuItems; (iii) Customer and an Order.

Your answer is saved locally in your browser.
Model answer & rubric
  • (i) Restaurant — MenuItems: Aggregation. A menu item is defined by the restaurant, but it is a reference-able catalogue entry that can exist and be reasoned about independently of any single order, and conceptually could be shared/duplicated across menus. The restaurant is a "whole" that groups items it doesn't destroy-on-delete in the strong sense. (Composition is also defensible if you argue a menu item has no meaning outside its restaurant and dies with it — award marks for either with a lifetime argument.)
  • (ii) Order — MenuItems: Aggregation / Association. An order refers to menu items the customer selected; those items (the catalogue entries) outlive the order and are shared with the restaurant's menu. The order does not own them — deleting the order must not delete the menu item — so this is aggregation, not composition. (If you model the order as holding its own OrderLine copies, those lines would be composition; the catalogue MenuItem reference is aggregation.)
  • (iii) Customer — Order: Composition, if you make the lifetime assumption explicit. This is the expected exam answer, but "the order exists for that customer" is not by itself a lifetime argument — composition requires you to actually assume the order's lifecycle is bound to the customer's, i.e. deleting the customer deletes their orders too. State that assumption and composition is defensible: strong ownership, and an order arguably has no meaning without the customer who placed it. Be aware, though, that most real systems keep orders around after a customer account is deleted, for auditing and legal reasons — under that (arguably more realistic) assumption the order outlives the customer, which makes this association or aggregation instead. Either answer earns marks, provided you state the lifetime assumption you're relying on rather than asserting the label.

Across all three the examiner is marking the lifetime/ownership justification, not the label alone. See association / aggregation / composition.

Marking rubric
  • ✔ Full credit (6): 2 per pair — a defensible relationship with a lifetime/ownership justification. Either side of the genuinely ambiguous calls scores full marks if justified — for (iii) that means naming the assumption (does deleting the customer delete the order, or not?) rather than just asserting "composition" because the order "belongs to" the customer.
  • ◐ Partial (3): correct labels but justified only by "has-a".
  • ✘ Common mistake: calling Order–MenuItem composition on the grounds "an order contains items" — the menu item outlives the order and is shared, so ownership/lifetime rules it out. Likewise, calling Customer–Order composition purely because "the order belongs to the customer" without ever stating whether the order's lifetime is actually bound to the customer's.
Q1·C(i) Menu item inheritance hierarchy [5 marks]

Model FoodItem and DrinkItem, which share name/price but have specific attributes (spiceLevel, volume). Propose at least 2 properties per class in the hierarchy, and methods that could be overridden.

Your answer is saved locally in your browser.
Model answer & rubric
  • abstract class MenuItem — shared: Name (string), Price (decimal); declares abstract string GetDescription() and maybe virtual decimal GetPreparationTime().
  • FoodItem : MenuItem — specific: SpiceLevel (enum), IsVegetarian (bool); overrides GetDescription() to include spice/dietary info and GetPreparationTime() (cooking takes longer).
  • DrinkItem : MenuItem — specific: Volume (ml), IsCarbonated (bool); overrides GetDescription() to include size, and might override GetPreparationTime() to near-zero.

The design point to state: common state lives once on the abstract base; the subtype-specific attributes and the differing behaviour (GetDescription) justify the inheritance. See inheritance.

Marking rubric
  • ✔ Full credit (5): a base MenuItem holding name/price (1); ≥2 specific properties per subclass (2); at least one sensibly overridden method with a reason it differs (2).
  • ◐ Partial (2–3): subclasses given but shared data duplicated instead of pulled to the base, or no overridable method.
  • ✘ Common mistake: putting spiceLevel on the base (drinks don't have one) or forgetting the overridden method the question explicitly asks for.
Q1·C(ii) UML of the menu hierarchy + Order [4 marks]

Draw a UML class diagram showing the inheritance hierarchy and how Order interacts with the abstract MenuItem and its subclasses. Sketch it first.

Model diagram & common drawing mistakes Order items : MenuItem[*] MenuItem «abstract» Name, Price, GetDescription() FoodItem SpiceLevel DrinkItem Volume contains ▸ 1..*

Full marks: abstract MenuItem (italic/«abstract») with the two concrete subclasses joined by hollow-triangle inheritance arrows, and Order linked to MenuItem by an aggregation (open diamond at the Order end) with 1..* — Order interacts with the abstraction, not the concrete subclasses.

Common drawing mistakes
  • ✘ Linking Order to FoodItem/DrinkItem directly instead of to the abstract MenuItem (the whole point is polymorphic interaction with the base).
  • ✘ Solid arrowhead for inheritance instead of a hollow triangle.
  • ✘ Filled diamond (composition) at Order — the items outlive the order, so aggregation (open diamond).
Marking rubric
  • ✔ Full credit (4): correct inheritance triangles (2), Order→abstract MenuItem relationship with correct diamond/multiplicity (2).
  • ◐ Partial (2): hierarchy correct but Order wired to concretes or wrong notation.

Question 2 — Library Management System

Q2·A(i) Creational pattern for resource creation [4 marks]

The library creates resources (Books, Journals, DVDs; later E-Books, Audiobooks) without client code (Librarian, CatalogManager) knowing each exact class. Propose a design using the most suitable creational pattern.

Your answer is saved locally in your browser.
Model answer & rubric

Use a Factory — Factory Method (or a Simple Factory) — producing an abstract LibraryResource. Define abstract class LibraryResource (or IResource) with the common contract, and a ResourceFactory.Create(ResourceType type) (or a CreateResource() factory method) that returns the right concrete Book/Journal/DVD behind that abstraction. The Librarian and CatalogManager call the factory and receive a LibraryResource reference — they never name a concrete class. See Factory Method.

Marking rubric
  • ✔ Full credit (4): names a Factory (Method/Simple), defines the abstract product, and shows the client depends only on the abstraction.
  • ◐ Partial (2): names "factory" but leaves clients depending on concrete types.
  • ✘ Common mistake: proposing Builder (there's no complex multi-part assembly here) or Abstract Factory (no family of matching products — just one resource at a time).
Q2·A(ii) Justify the choice via OCP [6 marks]

Justify your pattern choice and explain how it solves the problem of creating new objects flexibly, adhering to the Open/Closed Principle.

Your answer is saved locally in your browser.
Model answer & rubric

The factory centralises and encapsulates the instantiation decision, so the knowledge of which concrete class to build lives in exactly one place instead of being scattered across the Librarian and CatalogManager. Because those clients depend on the abstract LibraryResource and delegate creation to the factory, they are decoupled from the concrete types.

OCP link: adding an EBook or Audiobook means writing a new subclass and registering it with the factory — the client code that uses resources is not modified. The system is open to extension (new resource classes) and closed to modification (existing catalogue/lending code untouched). A pure Factory Method goes further: a new resource can arrive via a new creator subclass with no edit to the factory's existing branches at all. Contrast the naive approach — a switch(type){ new Book()… } pasted wherever resources are created — which must be edited in many places for every new type, the OCP violation the factory removes. See OCP and Factory Method.

Marking rubric
  • ✔ Full credit (6): 2 for the justification (encapsulates creation, clients depend on the abstraction); 2 for the OCP mechanism (new type = new class, clients unchanged); 2 for contrasting the scattered-switch anti-pattern / naming why existing code stays closed.
  • ◐ Partial (3): says "it's flexible" without tying flexibility to OCP and unchanged client code.
  • ✘ Common mistake: claiming full OCP while leaving a switch inside the factory that must be edited per type — acknowledge the trade-off, or use a registry/Factory Method to truly close it.
Q2·B Structural pattern for the physical hierarchy [4 marks]

Resources sit on shelves, shelves group into sections, and a section or shelf can contain other sections, shelves or individual resources. Propose the most suitable structural pattern.

Your answer is saved locally in your browser.
Model answer & rubric

Composite. This is a part-whole hierarchy where a container (Section, Shelf) can hold both leaves (individual Resources) and other containers, arbitrarily deep — the defining shape of Composite. Define interface ILibraryComponent { Display(); GetItemCount(); }; Resource is a leaf, while Shelf and Section are composites holding a list of ILibraryComponent and recursing into their children. The client then treats a single book and a whole section uniformlyGetItemCount() works the same on either, with the composite summing over its children.

Marking rubric
  • ✔ Full credit (4): names Composite, identifies leaf (Resource) vs composite (Shelf/Section), and states the uniform-treatment benefit / recursion.
  • ◐ Partial (2): names Composite but doesn't distinguish leaf from composite or explain uniform treatment.
  • ✘ Common mistake: proposing Decorator — it wraps one child to add behaviour; here a container holds many children in a tree, which is Composite.
Q2·C(i) Behavioural pattern for notifications [3 marks]

The system notifies users when a requested book becomes available. Propose and explain a suitable behavioural pattern; identify the classes and describe their interaction.

Your answer is saved locally in your browser.
Model answer & rubric

Observer. One-to-many "notify interested parties when state changes" is exactly Observer. The Book (or a BookAvailability wrapper) is the Subject; users who requested it are Observers (implementing IObserver.Update()) that subscribe via Attach(). When the book's status flips to available, the subject iterates its observer list and calls Update() on each, pushing a notification. The subject knows only the IObserver interface, so users can be added/removed at run time without the book changing — decoupled one-to-many notification.

Marking rubric
  • ✔ Full credit (3): names Observer (1), identifies Subject=Book and Observers=users (1), describes the subscribe/notify interaction (1).
  • ◐ Partial (1–2): names Observer but the roles or the notify flow are vague.
  • ✘ Common mistake: proposing Mediator (that coordinates peers) or a Publish/Subscribe broker — for an in-process, one-subject-many-watchers case, Observer is the precise fit.
Q2·C(ii) Behavioural pattern for borrowResource() [3 marks]

Borrowing has fixed steps — check eligibility, verify availability, update status. Propose a behavioural pattern for borrowResource(); identify the base class and the steps concrete subclasses implement (e.g. for different resource types).

Your answer is saved locally in your browser.
Model answer & rubric

Template Method. A fixed sequence of steps where the order is invariant but some steps vary by resource type is the signature of Template Method. The base abstract class ResourceLoan defines the public void BorrowResource() — the template — which calls CheckEligibility(), VerifyAvailability(), UpdateStatus() in that order. The skeleton is written once and sealed; the steps that differ (e.g. a DVD has a shorter loan period, a reference book can't be borrowed) are abstract/ virtual hooks overridden by BookLoan, DvdLoan, etc. The base controls the algorithm's shape; subclasses fill in the variable steps — inversion of control ("don't call us, we'll call you").

Marking rubric
  • ✔ Full credit (3): names Template Method (1), identifies the base class holding the fixed borrowResource() skeleton (1), names the overridable steps in subclasses (1).
  • ◐ Partial (1–2): names the pattern but doesn't separate the fixed skeleton from the varying steps.
  • ✘ Common mistake: proposing Strategy — Strategy swaps a whole algorithm at run time; here the algorithm's skeleton is fixed and only steps vary, which is Template Method. See Template Method vs Strategy.
Q2·D Integrated library UML [5 marks]

Draw a complete UML class diagram integrating the creational, structural and behavioural patterns from A, B and C, showing inheritance, composition, aggregation and association. Sketch first.

Model diagram & common drawing mistakes ResourceFactory Create() : LibraryResource LibraryResource «abstract» Book DVD «interface» ILibraryComponent Section Shelf «interface» IObserver Update() User (Observer) ResourceLoan «abstract» BorrowResource() [template] creates leaf ▸ notifies

Full marks: the diagram must make all three patterns legible at once — the ResourceFactory creating an abstract LibraryResource (creational); a Section/Shelf composite over ILibraryComponent with resources as leaves (structural); and a Book subject notifying User observers plus an abstract ResourceLoan template (behavioural) — with correct notation for inheritance (hollow triangle), realization (dashed triangle), composition/aggregation (diamonds) and association.

Common drawing mistakes
  • ✘ Showing only one or two patterns — the question explicitly wants all three collaborating.
  • ✘ Mixing realization (dashed) and inheritance (solid) triangles.
  • ✘ Diamonds on the wrong end for the composite (the diamond sits at the container Section/Shelf).
  • ✘ No multiplicities on the composite's child collection.
Marking rubric
  • ✔ Full credit (5): all three patterns present and correctly notated (~1.5 each) plus correct relationship types tying them together (~0.5).
  • ◐ Partial (2–3): patterns present but notation errors, or one pattern missing.

Question 3 — Composition over Inheritance, Clean Code & TDD

Q3·A(i) Issues with the inheritance-only payment design [4 marks]

A team lead proposes a base PaymentMethod class with each method (CreditCardPayment, PayPalPayment…) as a subclass. Analyse this from a class-inheritance perspective — what are the potential issues, especially as new payment methods are added?

Your answer is saved locally in your browser.
Model answer & rubric

Inheritance-only here is workable for the simplest case but reveals classic weaknesses as the hierarchy grows:

  • Combinatorial explosion / rigidity along a second axis. The moment a second dimension appears — e.g. payment method × currency, or method × fraud-check policy — pure subclassing multiplies: CreditCardUsdWithFraudCheck, PayPalEurNoFraud… The tree balloons.
  • Tight coupling to the base class. Every subclass inherits the base's implementation; a change to PaymentMethod ripples into all subclasses (the fragile base class problem), and behaviour is fixed at compile time — you can't change how an object pays at run time.
  • Weak reuse / duplication. Behaviour shared by some but not all methods has nowhere clean to live — it gets duplicated across subclasses or forced up into a bloated base.
  • LSP risk. If a future "method" can't honour part of the base contract (e.g. a store-credit method that can't refund), forcing it under PaymentMethod invites Liskov violations.

The through-line: inheritance binds behaviour statically and along a single axis, so it doesn't flex as requirements grow in more than one direction. See composition vs inheritance and LSP.

Marking rubric
  • ✔ Full credit (4): at least two distinct, well-argued issues (rigidity/explosion, base-class coupling, compile-time binding, duplication or LSP risk).
  • ◐ Partial (2): one issue, or issues listed without explaining why adding methods worsens them.
  • ✘ Common mistake: answering "inheritance is bad" with no concrete failure mode — the marks are in the specific weaknesses.
Q3·A(ii) Composition-based alternative [6 marks]

Propose an alternative that favours object composition over class inheritance, using an interface/abstract class for payment processing. Justify why it is more flexible and maintainable, directly addressing "favour object composition over class inheritance".

Your answer is saved locally in your browser.
Model answer & rubric

Design: define interface IPaymentStrategy { Result Pay(decimal amount); }. Each method — CreditCardStrategy, PayPalStrategy, DigitalWalletStrategyimplements it. A high-level PaymentProcessor (or the Order) holds an IPaymentStrategy and delegates: _strategy.Pay(total). The concrete strategy is injected, so it can be chosen — and changed — at run time (Strategy pattern). The processor "has-a" payment behaviour rather than "is-a" payment.

Why more flexible/maintainable (address the maxim directly): composition assembles behaviour from interchangeable parts instead of freezing it in a class hierarchy. (1) Run-time flexibility — a customer can switch from card to wallet on the same order by swapping the strategy object; inheritance would need a different subclass instance. (2) Open/Closed — a new method is a new strategy class the processor never knows about; nothing existing is modified. (3) No base-class coupling / no explosion — a second axis (currency, fraud policy) becomes another injected collaborator, not a multiplied subclass. (4) Testability — inject a fake strategy to test the processor. Composition keeps the pieces small, independent and recombinable, which is precisely why the GoF advise favouring it over inheritance. See composition over inheritance and DIP.

Marking rubric
  • ✔ Full credit (6): 2 for the composition design (interface + injected strategy, "has-a" delegation); 2 for run-time flexibility / swappability; 2 for tying it to OCP + maintainability and explicitly contrasting with the inheritance rigidity from A(i).
  • ◐ Partial (3): shows an interface but keeps new ConcreteStrategy() inside the processor (no real composition/injection), or doesn't justify against the maxim.
  • ✘ Common mistake: presenting the interface but still selecting the implementation with a switch in the processor — that reintroduces the coupling composition was meant to remove.
Q3·B Clean-code critique of the given function

Consider the code below. It is given in Python in the paper; the equivalent C# is provided alongside so you can reason in either language — the deficiencies are identical in both. Read it, then answer (i) and (ii).

1.
2. def process_data_and_save(data_list, user_id, file_path, log_status):
3.     """
4.     Processes a list of data, validates it, calculates a sum, saves it to a file,
5.     and updates a global status.
6.     """
7.     if len(data_list) > 0:
8.         total_sum = 0
9.
10.        # Data validation
11.        if all(isinstance(item, (int, float)) for item in data_list):
12.            for value in data_list:
13.                total_sum += value
14.
15.            # File I/O
16.            try:
17.                with open(file_path, 'w') as file:
18.                    file.write(f"User: {user_id}\n")
19.                    file.write(f"Data: {data_list}\n")
20.                    file.write(f"Total Sum: {total_sum}\n")
21.
22.                    global_status_record = {}
23.                    global_status_record['last_user'] = user_id
24.                    global_status_record['last_sum'] = total_sum
25.                    global_status_record['log_path'] = file_path
26.                    log_status.update(global_status_record)
27.
28.                    print("Data processed and saved successfully.")
29.                    return True
30.            except IOError as e:
31.                print(f"Error saving file: {e}")
32.                return False
33.        else:
34.            print("Error: All data items must be numbers.")
35.            return False
36.    else:
37.        print("Error: Data list cannot be empty.")
38.        return False
39.
40.    # Example usage
41.    global_log = {}
42.    sample_data = [10, 20, 30]
43.    process_data_and_save(sample_data, 123, 'report.txt', global_log)
44.
45.    print("\nGlobal Log:", global_log)
// Faithful C# translation — same structure, same deficiencies.
public bool ProcessDataAndSave(
    List<object> dataList, int userId, string filePath,
    Dictionary<string, object> logStatus)
{
    if (dataList.Count > 0)
    {
        double totalSum = 0;

        // Data validation
        if (dataList.All(item => item is int || item is double))
        {
            foreach (var value in dataList)
                totalSum += Convert.ToDouble(value);

            // File I/O
            try
            {
                using var file = new StreamWriter(filePath);
                file.WriteLine($"User: {userId}");
                file.WriteLine($"Data: {string.Join(\",\", dataList)}");
                file.WriteLine($"Total Sum: {totalSum}");

                var record = new Dictionary<string, object>
                {
                    ["last_user"] = userId,
                    ["last_sum"]  = totalSum,
                    ["log_path"]  = filePath
                };
                foreach (var kv in record) logStatus[kv.Key] = kv.Value; // ← mutates caller's dictionary

                Console.WriteLine("Data processed and saved successfully.");
                return true;
            }
            catch (IOException e)
            {
                Console.WriteLine($"Error saving file: {e}");
                return false;
            }
        }
        else
        {
            Console.WriteLine("Error: All data items must be numbers.");
            return false;
        }
    }
    else
    {
        Console.WriteLine("Error: Data list cannot be empty.");
        return false;
    }
}
Q3·B(i) Three clean-code deficiencies [4 marks]

List and briefly explain three deficiencies related to clean-code principles, stating the line numbers (of the Python listing) that contain them.

Your answer is saved locally in your browser.
Model answer & rubric

Any three of the following, each named as a principle with lines cited:

  • Function does too many things (SRP for functions / low cohesion). One function validates, sums, does file I/O, mutates a status record and prints — at least four jobs (lines 11–13 validation/sum, 17–20 I/O, 22–26 status update, 28/31/34/37 printing). A clean function does one thing.
  • Deep nesting / arrow anti-pattern. Nested if/if/try drive the code four levels deep (lines 7, 11, 16, and the trailing elses at 33/36). Guard clauses (early return on empty/invalid) would flatten it.
  • Poor / vague naming and a misleading name. process_data_and_save announces it does two things (the "and" is a smell); log_status, data_list are vague; the docstring even admits "updates a global status" (lines 2, 5, 26).
  • Long parameter list. Four parameters, one of them an output parameter (lines 2, 26) — hard to call correctly and a sign the function should be split or take an object.
  • Dead / unreachable code & wrong scope. The "Example usage" (lines 40–45) is indented inside the function body after all branches return, so it can never run — it also calls the function recursively-looking at module level but sits in the wrong scope. Confusing and dead.
  • Magic values / booleans-for-errors. Returning True/False to signal outcomes (lines 29, 32, 35, 38) instead of raising exceptions mixes control flow with error handling.

See clean functions (do one thing, small, few arguments) and naming/structure.

Marking rubric
  • ✔ Full credit (4): three distinct deficiencies, each named as a principle, briefly explained, with line numbers. Roughly 1.3 marks each — and the line-number citation is explicitly required, so an answer with no lines is capped.
  • ◐ Partial (2): three issues but no line numbers, or two of them are really the same issue.
  • ✘ Common mistake: listing three flavours of one problem (e.g. three naming complaints) — the examiner wants three distinct clean-code principles.
Q3·B(ii) The side effect [4 marks]

The function has a significant side effect. Identify the specific line(s) where it occurs, explain why it is a side effect, and discuss the main problems it introduces.

Your answer is saved locally in your browser.
Model answer & rubric

Where: line 26log_status.update(global_status_record) (built up on lines 22–25). This mutates the log_status dictionary that was passed in by the caller. (Writing to the file, lines 17–20, and the prints are also side effects; but the "significant" one the question targets is the hidden mutation of the caller-owned argument.)

Why it's a side effect: a function's observable output should ideally come through its return value. Here the function also reaches out and modifies state that lives outside its local scope — an object owned by the caller — as a hidden consequence of being called. The function's name promises "process and save"; nothing signals that it will silently rewrite the caller's dictionary. Its result is not conveyed solely by what it returns.

Problems it introduces:

  • Hidden coupling & surprise. Callers who don't read the body won't know their dictionary changed — a source of bugs at a distance.
  • Harder to test & reason about. The function isn't referentially transparent: the same inputs also depend on and alter external state, so you must set up and inspect that state to test it. Order of calls now matters.
  • Concurrency hazards. Shared mutable state touched as a side effect is unsafe if two callers share the dictionary.
  • Reduced reusability. The mutation entangles this function with the caller's data model.

The fix: return the status record and let the caller decide whether to merge it — keep the function free of hidden state changes. See side effects.

Marking rubric
  • ✔ Full credit (4): 1 for the correct line (26, the log_status mutation; build-up 22–25 accepted); 1.5 for a correct definition of side effect (modifies external/caller state, output not via return); 1.5 for at least two real problems (hidden coupling, testability, concurrency, reasoning).
  • ◐ Partial (2): points at file I/O or prints only, or names "side effect" without the caller-mutation insight.
  • ✘ Common mistake: saying the side effect is "it returns True/False" — a return value is the opposite of a side effect. The side effect is the out-of-scope mutation.
Q3·C(i) The three steps of the TDD cycle [3 marks]

Explain the three main steps of the TDD cycle. For each, describe the primary goal and what a developer does.

Your answer is saved locally in your browser.
Model answer & rubric

Red → Green → Refactor, for the password-validator example:

  • Red — write a failing test. Goal: specify the next tiny piece of behaviour before any production code. The developer writes a test for one rule (e.g. "rejects a password under 8 characters"), runs it, and watches it fail — proving the test actually exercises missing behaviour (and isn't a false pass).
  • Green — make it pass. Goal: get to passing as quickly as possible. The developer writes the minimum production code needed to satisfy that one test — no more — and runs the suite until it is green. Resisting the urge to over-build is the discipline.
  • Refactor — clean up. Goal: improve the design now that tests protect you. The developer removes duplication and improves names/structure in both test and production code, re-running the tests to confirm nothing broke. Behaviour is unchanged; quality improves.

Then repeat for the next rule. See the TDD cycle.

Marking rubric
  • ✔ Full credit (3): all three steps named, each with its goal and the developer action — 1 mark per step. Key nuances: Red must fail first; Green is the minimum code; Refactor keeps tests green.
  • ◐ Partial (1–2): steps named but goal/action blurred (e.g. "write code then test").
  • ✘ Common mistake: writing code first and adding tests after — that's test-after, not TDD. The test comes first and must fail.
Q3·C(ii) Two design benefits of TDD [4 marks]

Beyond "the code works", discuss two benefits of TDD that improve overall software design. How does TDD encourage more modular, maintainable code?

Your answer is saved locally in your browser.
Model answer & rubric

Two design-level benefits (not just "fewer bugs"):

  • Testability pressure forces good structure (modularity + loose coupling). To write a test first, the unit must be instantiable and its dependencies substitutable — which pushes you towards small classes, single responsibilities, and dependency injection (depending on interfaces so you can pass fakes). Hard-to-test code is a design smell TDD surfaces immediately, so the design comes out more modular and decoupled almost as a by-product.
  • A safety net that enables fearless refactoring (maintainability). The growing test suite captures intended behaviour, so you can restructure and clean up continuously without fear of silent breakage. That keeps the design from rotting over time — maintainability is preserved because change is cheap and safe. The tests also serve as executable documentation of how each unit is meant to be used.

A third, if you have room: YAGNI/minimalism — writing only enough code to pass keeps the design lean, avoiding speculative complexity. See TDD.

Marking rubric
  • ✔ Full credit (4): two design benefits, each explained with the mechanism — 2 each. The strongest pair is "testability forces decoupling/modularity" and "tests enable safe refactoring / documentation".
  • ◐ Partial (2): benefits stated but tied to "catches bugs" rather than to design quality.
  • ✘ Common mistake: giving only correctness/QA benefits — the question explicitly asks about design, so modularity, coupling and maintainability must feature.

Question 4 — Enterprise & Distributed Architecture

Q4·A SOA vs MSA [5 marks]

Compare Service-Oriented Architecture and Microservices Architecture across: service granularity; communication style; data management; technology heterogeneity.

Your answer is saved locally in your browser.
Model answer & rubric
DimensionSOAMicroservices (MSA)
Service granularityCoarse-grained — larger, often enterprise-wide services covering broad business functionsFine-grained — small services each owning a single business capability
CommunicationOften via a centralised smart ESB (Enterprise Service Bus); heavier protocols (SOAP/WS-*); "smart pipes"Decentralised, lightweight — "smart endpoints, dumb pipes"; REST/HTTP and asynchronous messaging; no central bus
Data managementTends to share data / a common database across services Database-per-service — each service owns its data privately (decentralised data)
Technology heterogeneityMore standardised; often a shared platform/protocol stack Polyglot — each service can choose its own language, framework and datastore

One-line summary worth stating: MSA is essentially SOA taken to a fine-grained, decentralised extreme — small independently-deployable services, private data, dumb pipes. See microservices vs SOA and SOA.

Marking rubric
  • ✔ Full credit (5): a correct SOA-vs-MSA contrast on each of the four dimensions (~1 each) plus coherence (~1) — granularity coarse vs fine, ESB vs dumb pipes, shared vs per-service data, standardised vs polyglot.
  • ◐ Partial (2–3): some dimensions correct but data management or communication muddled.
  • ✘ Common mistake: claiming SOA and MSA are "the same thing" — they share SOA principles but differ sharply on granularity, the ESB, and data ownership.
Q4·B MVC mapped onto 3-Tier [6 marks]

Explain how MVC's components map to the layers of the 3-Tier Architecture. Which tier does MVC primarily focus on, and why is that distinction important for web applications?

Your answer is saved locally in your browser.
Model answer & rubric

The mapping (with a key caveat):

MVC component3-Tier layerRole
ViewPresentation tierRenders the UI the user sees
ControllerPresentation tierHandles input, orchestrates the response
ModelBusiness-logic tier (and reaches into the Data tier)Domain logic, rules, and the data it operates on

Which tier MVC primarily focuses on: the Presentation tier. MVC is fundamentally a presentation-layer pattern — its concern is organising how a request is received, how logic is invoked, and how output is rendered. The View and Controller live entirely in presentation; the Model is MVC's link to the business/data tiers but MVC itself doesn't prescribe how those deeper tiers are built.

Why the distinction matters for web apps: the two are different kinds of decomposition, not rivals — 3-Tier is a physical/deployment layering (presentation ▸ logic ▸ data, often on separate hosts for scalability), while MVC is a logical organisation within the presentation tier. Recognising that MVC sits inside the presentation tier means you don't mistake "the Model" for the whole business/data tier: for a responsive, scalable web app you still layer the system into tiers (so the data and logic tiers scale independently) and apply MVC in the presentation tier to keep UI, input-handling and view rendering cleanly separated. Conflating them leads to fat controllers stuffed with business logic that should live in a separate tier. See MVC, 3-tier, and the MVC vs 3-tier comparison.

Marking rubric
  • ✔ Full credit (6): 3 for a correct component→layer mapping (View/Controller ▸ presentation, Model ▸ business/data); 1 for naming presentation as the primary focus; 2 for why it matters — MVC is logical organisation within a tier vs 3-tier's physical layering, and the consequence for scalable web apps.
  • ◐ Partial (3): maps the components but says MVC "replaces" 3-tier or misses the primary-tier point.
  • ✘ Common mistake: equating Model with the entire data tier and treating MVC and 3-tier as the same three-way split — they operate at different levels (logical vs physical).
Q4·C RMI vs CORBA [8 marks]

Compare RMI and CORBA across: (i) language dependency; (ii) platform interoperability; (iii) interface definition; (iv) ease of use.

Your answer is saved locally in your browser.
Model answer & rubric
DimensionJava RMICORBA
(i) Language dependencyJava-specific — both ends are (traditionally) Java Language-independent — designed for multi-language systems (C++, Java, Python…) via language mappings
(ii) Platform interoperabilityRuns anywhere a JVM runs, but effectively Java-to-Java; interoperability comes from the JVM, not from crossing languages Strong cross-platform and cross-language interoperability via the ORB and the IIOP wire protocol
(iii) Interface definitionInterfaces are plain Java interfaces (extend java.rmi.Remote) — no separate IDL Interfaces defined in a neutral IDL (Interface Definition Language), compiled to stubs/skeletons per target language
(iv) Ease of useGenerally easier for a typical developer — if you're already in Java it's simple, no separate IDL, natural object model More complex — you must write IDL, run an IDL compiler, and deal with the ORB; heavier but more powerful for heterogeneous systems

The essence: RMI trades interoperability for simplicity within Java; CORBA pays extra complexity (IDL + ORB) to buy genuine language/platform independence. See distributed objects / RPC.

Marking rubric
  • ✔ Full credit (8): 2 marks per dimension — Java-only vs language-neutral; JVM-bound vs ORB/IIOP cross-platform; Java interfaces vs neutral IDL; easier-in-Java vs more-complex-but-portable.
  • ◐ Partial (4): two or three dimensions correct; IDL vs no-IDL is the one most often missed.
  • ✘ Common mistake: saying RMI is language-independent (it isn't, traditionally) or forgetting that CORBA's defining feature is IDL-based, ORB-mediated interoperability.
Q4·D MSA communication styles for the ride-sharing app [6 marks]

For each scenario, recommend the most suitable communication style (one-to-one/one-to-many, synchronous/asynchronous) and justify: (i) Driver accepting a trip — request one driver, need an immediate accept/decline; (ii) Customer requesting a ride — notify many nearby drivers at once, no immediate response needed; (iii) Trip completion — charge Payment then notify the customer, must know payment succeeded before proceeding.

Your answer is saved locally in your browser.
Model answer & rubric
  • (i) Driver accepting a trip — One-to-one, Synchronous (request/response). Trip Management sends a request to a single Driver service and blocks for an immediate accept/decline. The need for one specific recipient and a prompt reply that gates the next step is the definition of synchronous request/response.
  • (ii) Customer requesting a ride — One-to-many, Asynchronous (publish/notify). The system must notify multiple nearby drivers simultaneously and does not need an immediate reply from all — a broadcast/publish. Asynchronous one-to-many (e.g. publish/subscribe) fits: fire the notification to all nearby drivers and let acceptances arrive later, without blocking.
  • (iii) Trip completion — One-to-one, Synchronous for the payment; then notify. Trip Management must charge Payment and know it succeeded before proceeding — that ordering/confirmation requirement demands a synchronous one-to-one request/response to the Payment service. Only after the success confirmation does it send the customer notification (which itself can be asynchronous fire-and-forget). The "must confirm before continuing" phrase is the tell for synchronous.

The deciding questions throughout: one recipient or many? and do I need the answer before I can continue? "Need an immediate/confirmed response" ⇒ synchronous; "notify many, don't wait" ⇒ asynchronous one-to-many. See event-driven / async messaging and microservices communication.

Marking rubric
  • ✔ Full credit (6): 2 per scenario — correct style and a justification tied to the "one vs many" and "need immediate response?" cues. (i) sync 1:1; (ii) async 1:many; (iii) sync 1:1 for payment (must confirm first).
  • ◐ Partial (3): correct styles but justified only by restating the scenario, not the underlying cue.
  • ✘ Common mistake: making (ii) synchronous (it explicitly needs no immediate response) or (iii) asynchronous for the payment (it explicitly must confirm success before proceeding).

Bonus paper — MSA patterns (written for this site, not a past paper)

⚠️ Be clear about what this is

Everything above this point is a real assessment, reproduced faithfully. This section is not. The 2025 final paper predates the Architectural and Design Patterns used in the Microservices Architecture lecture, so nothing above tests API Gateway code, sagas, CQRS, sidecars, idempotency or dead letter queues. These five questions are written in the same house style and mark allocation as Question 4 of the real paper, to give that material somewhere to be examined. Treat them as a well-informed guess at the shape, not as leaked questions. Source material: Chapter 11. 25 marks · 30 minutes.

Q5·A Circuit breaker states, and a critique [6 marks]

(i) Name and describe the three states of the Circuit Breaker pattern, including what happens to a request in each and what causes each transition. (ii) A developer implements the breaker with a single failure counter and an isOpen flag stored as static fields shared by the whole process, set to true once five failures have ever occurred. Identify three defects in this implementation.

Your answer is saved locally in your browser.
Model answer & rubric

(i) The three states. Closed — requests flow normally and failures are counted; when the failure count/rate crosses the threshold the breaker trips to Open. Open — requests fail immediately without any network call being attempted, so no connection or thread is held; after a cooldown period elapses the breaker moves to Half-Open. Half-Open — exactly one trial request is admitted: if it succeeds the breaker returns to Closed and counters reset, if it fails the breaker returns to Open and the cooldown restarts.

(ii) Three defects. (1) The state is global. One flaky dependency trips the breaker for every other dependency in the process, so a failing payment provider also blocks calls to a perfectly healthy inventory service. Breaker state must be per-dependency. (2) There is no half-open state and no cooldown — once open, always open, so a two-second outage becomes a permanent one and the implementation contradicts the pattern it claims to implement. (3) The counter is cumulative, not windowed. Five failures spread over a month trip it exactly as surely as five in a row; real breakers track a failure rate over a rolling window and reset on success.

Creditworthy extras: no thread-safety around the shared counter; no fallback, so the caller still has no answer for the user; hand-rolling it at all when Microsoft.Extensions.Http.Resilience/Polly provides per-client policies. See Chapter 11 · Circuit Breaker and Chapter 9.

Marking rubric
  • ✔ Full credit (6): 3 marks for the three states with their transitions (not just the names), 3 marks for three distinct defects — the global state and the missing half-open/cooldown are the two that must appear.
  • ◐ Partial (3): states listed correctly but described as a list of names, or only one real defect found (usually "it's not thread-safe", which is true but the least important of the three).
  • ✘ Common mistake: describing Open as "requests are queued until the service recovers" — the whole point is that they fail fast, with no call attempted at all.
Q5·B Saga coordination for an online pharmacy [6 marks]

An online pharmacy places an order across four services: Order, Prescription-Verification, Payment and Dispatch. Verification can take minutes (a pharmacist reviews it), payment must be refunded if dispatch finds the medicine unavailable, and the compliance team must be able to show, for any order, exactly which step it reached. Recommend choreography or orchestration and justify your recommendation. State one disadvantage of your choice.

Your answer is saved locally in your browser.
Model answer & rubric

Orchestration. A central Order Saga Orchestrator issues commands (VerifyPrescription, ChargePayment, Dispatch) and drives the compensations (RefundPayment, CancelOrder) when a step fails. Justification tied to this scenario: (1) The compliance requirement is decisive. Orchestration persists the saga's state per order, so "which step did order 7781 reach?" is a single row to query; under choreography the answer only exists as a trail of events across four services' logs. (2) Verification takes minutes. A long-running step needs the saga's progress to survive restarts and deploys, which means persisted saga state and something that resumes it — exactly what an orchestrator provides. (3) Compensation ownership is explicit. When Dispatch reports the medicine unavailable, the orchestrator's saga state records which steps ran and therefore which undos are owed. Under choreography both Payment and Order subscribing to that failure event and each running its own compensation is perfectly correct — but nothing writes down who owns which undo, so with at-least-once redelivery and a growing workflow it is easy for a compensation to run twice or be dropped.

Disadvantage. The orchestrator is an additional service to build, deploy and monitor; it is a single point of failure for placing new orders, and a change bottleneck because every new business rule edits it. (Choreography's opposite trade — no new component, loose coupling — is what you are giving up.)

Marking rubric
  • ✔ Full credit (6): 4 marks for the recommendation with at least two mechanism-level reasons drawn from the scenario (auditability/queryable state, long-running step, compensation ownership); 2 marks for a genuine disadvantage of the chosen style.
  • ◐ Partial (3): correct choice justified with adjectives ("easier to manage", "more reliable") rather than mechanisms, or a disadvantage of the rejected style instead of the chosen one.
  • ✘ Common mistake: proposing a distributed transaction/2PC across the four databases — it requires every participant available at commit time, which is precisely the coupling microservices remove.
Q5·C Idempotency [5 marks]

(i) Define idempotency in the context of a microservice endpoint, and explain why POST /accounts/42/charge requires special handling while GET /orders/7781 does not. (ii) Describe the four steps of the idempotency-key workflow. (iii) State one implementation mistake that would leave the endpoint vulnerable despite using keys.

Your answer is saved locally in your browser.
Model answer & rubric

(i) An operation is idempotent if calling it more than once with the same input has no additional effect beyond the first call. This matters because distributed messaging is at-least-once, not exactly-once: brokers redeliver, and clients whose request timed out retry even when the server processed the original perfectly and merely lost the response. A GET changes nothing, so repetition is harmless. A POST … /charge creates a new effect each time — two deliveries charge the customer twice — so it needs a key that carries the caller's intent: "this is the same request, not another one."

(ii) The workflow.Check — is this key already in the store? ② Executing — if it is new, claim it with status "Processing" and run the business logic. ③ Finished — store the response body against the key with status "Completed" (typically with a TTL). ④ Replay — if the same key arrives again, return the stored response without running the logic again.

(iii) One mistake (any of these). Implementing steps ① and ② as separate operations — "check whether it exists, then insert" is a race that two simultaneous duplicates both win, so both charge; the claim must be atomic (a unique-constraint insert, or Redis SET NX). Alternatively: letting the claim, the side effect and the stored response commit separately, so a crash between "charged" and "recorded" strands the key half-finished and refuses every retry while the money has already moved; not scoping keys per caller, so two tenants collide; storing only "done" instead of the status code and the response body, so the retrying client gets a different answer from the original; or keys with no expiry, so the store grows forever.

Marking rubric
  • ✔ Full credit (5): 2 for the definition plus the at-least-once reason for the GET/POST contrast, 2 for all four workflow steps in order, 1 for a genuine implementation mistake.
  • ◐ Partial (2–3): defines idempotency correctly but explains the contrast as "GET is read-only" without mentioning retries/at-least-once delivery; or gives three of the four steps.
  • ✘ Common mistake: claiming idempotency means "the request only arrives once" — it means repeated arrivals cause no additional effect; you cannot prevent the duplicates, only neutralise them.
Q5·D CQRS [4 marks]

Explain what CQRS separates and why. State one benefit and one cost, and name the specific user-visible problem that the cost causes.

Your answer is saved locally in your browser.
Model answer & rubric

CQRS separates the model used to change state (commands: create/update/delete, returning success or an id rather than data) from the model used to read state (queries, returning data and changing nothing). The two jobs want opposite things: writes want normalisation, validation and enforced invariants; reads want denormalised, pre-joined shapes tuned for one screen — and reads usually outnumber writes by orders of magnitude.

Benefit: each side can be modelled, optimised and scaled independently — for example a relational write store with real transactions alongside a document store or cache tuned purely for read speed. Cost: the read model is kept up to date by a projector consuming events, so it is only eventually consistent, and there are now two stores plus a projector to build and monitor. The user-visible problem is read-your-own-writes: a user saves a change, the query hits a read model that has not caught up, and they are shown the old value — "I saved it and it didn't appear." Standard fixes are to return the new state directly from the command, or to serve that user's own recent reads from the write model.

Creditworthy: noting that CQRS does not require event sourcing, and that a separate read database is an optional escalation rather than part of the definition.

Marking rubric
  • ✔ Full credit (4): 2 for what is separated and why the two workloads conflict, 1 for a benefit, 1 for the cost named as eventual consistency / read-your-own-writes.
  • ◐ Partial (2): "separates reads from writes" with no account of why that helps or what it costs.
  • ✘ Common mistake: equating CQRS with event sourcing, or claiming it removes the need for transactions.
Q5·E Sidecar, and the health-probe trap [4 marks]

(i) Explain the Sidecar pattern and give two responsibilities typically moved into a sidecar. (ii) The slides say your service must be "sidecar-aware" by exposing health checks. A team puts a database connectivity check into their liveness probe. Explain what happens during a 30-second database outage, and what they should have done instead.

Your answer is saved locally in your browser.
Model answer & rubric

(i) A sidecar is a helper component deployed alongside the main service — in the same pod/deployment unit, sharing its lifecycle and local network — that takes over peripheral, cross-cutting concerns so the service contains only business logic. Typical responsibilities: mutual TLS between services, retries and timeouts on outgoing calls, metrics and distributed tracing, service discovery and load balancing. It is provided at the infrastructure level (Envoy, Istio) rather than written into the application, and the main benefit is that changing, say, the retry policy is one configuration change rather than a library bump and a redeploy of every service in every language.

(ii) A failing liveness probe means "this process is broken beyond recovery — restart it." So during the 30-second database outage every pod fails liveness simultaneously and the platform restarts all of them at once, turning a brief dependency blip into a full application outage — and the restarted pods still cannot reach the database, so they may crash-loop. Dependency checks belong in the readiness probe, which means "don't send me traffic right now": failing it removes the pod from the load balancer and puts it back automatically once the database recovers, with no restarts. Liveness should check only that the process itself is functioning.

Marking rubric
  • ✔ Full credit (4): 2 for the pattern with two genuine cross-cutting responsibilities, 2 for the liveness failure mode (mass simultaneous restart, blip becomes outage) and the readiness fix.
  • ◐ Partial (2): describes the sidecar correctly but answers (ii) as "the health check fails" without explaining that liveness triggers a restart while readiness only removes traffic.
  • ✘ Common mistake: listing business logic as something to move into the sidecar — cross-cutting infrastructure moves out; the domain rules are the one thing that must stay.

How you did — self-grading & where to go next

Mark each paper out of its total, then read the band you landed in. A mock is only useful if a dropped mark turns into a chapter you re-read — the pointers below map every question topic to the exact section that fixes it.

✅ What ≥70% looks like, per paper
  • Assignment 1 (Parking Lot, /100) — ≥70: you name each relationship and win the marks on the lifetime/ownership justification (Q3·F is 10 marks of pure justification); your SOLID answers give the mechanism (abstraction + new subclass, injected strategy, role interfaces), not just the principle's name; and your UML uses correct diamond ends and inheritance triangles. Below 70 usually means labels without justification, or OCP/ISP answered by naming rather than by showing the extension mechanism.
  • Assignment 2 (Patterns, /100) — ≥70: you pick the right pattern and can argue it against the look-alike (Builder not Abstract Factory; Composite vs Decorator; Adapter vs Proxy; Iterator not Strategy), your UML is correctly notated, and the comparison questions are laid out as genuine contrasts. Below 70 typically means correct pattern, thin "why not the alternative".
  • Final Exam 2025 (/100, four questions) — ≥70: you handle the full breadth — SOLID + UML (Q1), all three pattern families on one system (Q2), composition-over-inheritance + the clean-code/side-effect critique with line numbers + TDD (Q3), and the four architecture comparisons in crisp table form (Q4). Losing a whole sub-question (e.g. the side effect, or RMI-vs-CORBA's IDL point) is what drops you under 70.
  • Bonus MSA paper (/25) — ≥70: your circuit-breaker answer describes the transitions and not just the three names, your saga recommendation is justified by mechanisms drawn from the scenario rather than adjectives, and you can state the cost of CQRS as read-your-own-writes. Below 70 is almost always the same thing: naming patterns correctly but justifying them with vibes. Fix it at Chapter 11 and the saga drills in Studio 16 · §5.

Turn weak spots into a reading list:

🧠 The meta-skill these papers test

Notice how often the marks live in the second half of the answer: not "it's aggregation" but "because the item outlives the order"; not "use OCP" but "so the tested processing code is never edited"; not "Proxy" but "because access must be lazy, cached and authorised". Naming is table stakes. Across all three papers, the examiner is paying for justification against the tempting alternative — the same judgement the module quiz drills. When you can produce that argument unprompted, under time, you're ready.