Exercise 2: Adding a CancelledState — Possible Solution ==================================================================== THE NEW STATE AND METHOD ------------------------------ class CancelledState: name = 'Cancelled' def pay(self, order): raise Exception('Order is cancelled') def ship(self, order): raise Exception('Order is cancelled') def deliver(self, order): raise Exception('Order is cancelled') def cancel(self, order): raise Exception('Order already cancelled') A new cancel(self, order) method is added to EVERY existing state class (PendingState, PaidState, ShippedState, DeliveredState), each deciding for itself whether cancellation makes sense from that state: PendingState.cancel(order): order.state = CancelledState() # allowed PaidState.cancel(order): order.state = CancelledState() # allowed ShippedState.cancel(order): raise Exception('Cannot cancel a shipped order') DeliveredState.cancel(order): raise Exception('Cannot cancel a delivered order') OrderContext gets one new matching method, following its own existing one-line delegation pattern exactly: def cancel(self): self.state.cancel(self) VERIFYING A PENDING ORDER CAN BE CANCELLED ------------------------------ order1 initial status: Pending order1 status after cancel: Cancelled A fresh order, still in PendingState, transitions to CancelledState correctly when cancel() is called. VERIFYING A SHIPPED ORDER CANNOT BE CANCELLED ------------------------------ order2 status before cancel attempt: Shipped order2 cancel correctly blocked: Cannot cancel a shipped order order2 status after blocked cancel: Shipped An order moved through pay() and ship() into ShippedState correctly rejects cancel(), raising the expected exception - and its status is confirmed still 'Shipped' afterward, exactly matching this chapter's own established pattern of a blocked transition leaving no trace. WHY THIS FOLLOWS THE ESTABLISHED PATTERN CLEANLY ------------------------------ Adding a genuinely new capability (cancellation) required touching every existing state class once each, to state its own rule - but OrderContext.pay()/ship()/deliver() needed zero changes, and the new cancel() method is exactly as simple as the three that already existed. This is the same warn-box lesson from this chapter's own "Where the if/elif chain would have lived instead" callout, now demonstrated by actually adding a new transition rather than just describing what adding one would look like. WHY THIS WORKS AS AN ANSWER ------------------------------ The new state and method follow this chapter's own established per-state-class pattern exactly, both the allowed and blocked cancellation outcomes are verified with their resulting status checked afterward, and the design choice (which states permit cancellation) is stated as a rule owned by each individual state class, not by OrderContext itself.