Based on the requirements and use cases, identify the main objects of the system and analyze how they interact and relate to each other...
showtimes: movie name, time, theather
theater meta: theaterName, seats per theater
booking: userId, bookingId, timestamp
For each class, define the attributes (data) it will hold and the methods (functions) that operate on the attributes. Ensure they align with the object's responsibilities and adhere to the principle of encapsulation. Write your code in the code editor below.
from __future__ import annotations
from typing import List, Dict
from threading import Lock
from datetime import datetime
class Show:
def init(self, show_id: str, movie: Movie, screen: Screen, start_time: datetime):
self.show_id = show_id
self.movie = movie
self.screen = screen
self.start_time = start_time
self.seat_availability: Dict[Seat, SeatStatus] = {seat: SeatStatus.AVAILABLE for seat in screen.get_seats()}
self._lock = Lock()
def get_available_seats(self) -> List[Seat]:
with self._lock:
return [seat for seat, status in self.seat_availability.items() if status == SeatStatus.AVAILABLE]
def lock_seats(self, seats: List[Seat]) -> bool:
with self._lock:
if any(self.seat_availability[seat] != SeatStatus.AVAILABLE for seat in seats):
return False
for seat in seats:
self.seat_availability[seat] = SeatStatus.HELD
return True
def confirm_seats(self, seats: List[Seat]) -> None:
with self._lock:
for seat in seats:
self.seat_availability[seat] = SeatStatus.BOOKED
def release_seats(self, seats: List[Seat]) -> None:
with self._lock:
for seat in seats:
if self.seat_availability[seat] != SeatStatus.BOOKED:
self.seat_availability[seat] = SeatStatus.AVAILABLE
from future import annotations
from datetime import datetime, timedelta
from threading import RLock
from typing import List
class Booking:
HOLD_TIMEOUT_MINUTES = 10
def __init__(self, booking_id: str, show: Show, seats: List[Seat], user_id: str, pricing_strategy: PricingStrategy):
self.booking_id = booking_id
self.show = show
self.seats = seats.copy()
self.user_id = user_id
self.status = BookingStatus.PENDING
self.created_at = datetime.now()
self.total_amount = sum(pricing_strategy.calculate_price(seat, show) for seat in seats)
self.payment = None
self._lock = RLock()
def confirm(self, payment: Payment) -> bool:
with self._lock:
if self.status != BookingStatus.PENDING:
return False
if payment.status != PaymentStatus.SUCCESS:
return False
self.payment = payment
self.status = BookingStatus.CONFIRMED
self.show.confirm_seats(self.seats)
return True
def cancel(self) -> bool:
with self._lock:
if self.status != BookingStatus.CONFIRMED:
return False
self.status = BookingStatus.CANCELLED
self.show.release_seats(self.seats)
if self.payment:
self.payment.refund()
return True
def is_expired(self) -> bool:
return self.status == BookingStatus.PENDING and datetime.now() > self.created_at + timedelta(minutes=self.HOLD_TIMEOUT_MINUTES)
def expire(self):
with self._lock:
if self.is_expired():
self.status = BookingStatus.EXPIRED
self.show.release_seats(self.seats)
Explain design tradeoffs you considered. Check and explain whether your design adheres to SOLID principles. Explain how your design can handle changes in scale and whether it would be easy to extend with new functionalities. Identify areas for future improvement...
Without the Strategy pattern, Booking calculates prices with conditionals:
java
// Without Strategy - conditional pricing
double price = BASE_PRICES.get(seat.getSeatType());
if (isWeekend(show)) price *= 1.3;
if (isHoliday(show)) price *= 1.5;
if (hasLoyaltyCard(user)) price *= 0.9;
Three pricing rules means three if-statements. Adding a fourth means editing Booking. The Strategy pattern extracts each rule into its own class:
java
// With Strategy - clean delegation
double price = pricingStrategy.calculatePrice(seat, show);
Booking delegates to whichever strategy is injected. Adding HolidayPricing means one new class and zero changes to Booking.
Booking follows a strict lifecycle: PENDING, CONFIRMED, CANCELLED, EXPIRED. The confirm() method only works from PENDING. cancel() only works from CONFIRMED. Rather than scattering if-checks across methods, each status defines which operations are valid. Although the solution uses enum-based checks for simplicity, a full State pattern would extract each status into its own class.
Show.lockSeats() is the critical operation. It must atomically check that all requested seats are AVAILABLE and then set them to HELD. If any seat fails the check, none are modified. This atomic check-and-set runs inside a synchronized block on the Show's internal lock object.
Key Insight
The lock granularity is per-Show. Users booking seats for different shows never contend with each other. Only users competing for the same show at the same instant serialize. This keeps the contention window narrow.
Key Insight
Why not use per-Seat locks instead of per-Show locks? Because users select multiple seats together (A5 and A6). Per-seat locks would require acquiring two locks in the correct order to avoid deadlocks, and a failed second lock means releasing the first and retrying. A single per-Show lock makes the multi-seat check-and-set atomic with no deadlock risk.
Why does the Decorator pattern fit pricing better than inheritance? Consider three pricing rules: weekend, holiday, and loyalty. With inheritance you need seven subclasses to cover every combination. With decorators, you create three wrapper classes and stack them at runtime. The number of classes grows linearly instead of exponentially.
Single Responsibility Principle
Open/Closed Principle
Liskov Substitution Principle
Interface Segregation Principle
Dependency Inversion Principle
Below is the class diagram for the movie ticket booking system.
owns
owns
schedules
screens
uses
tracks availability
references
reserves
has
uses
implements
implements
wraps
Cinema
-String cinemaId
-String name
-List<Screen> screens
-List<Show> shows
+addScreen(Screen)
+addShow(Show)
+getShows(Movie, LocalDate) : List<Show>
Screen
-String screenId
-String name
-List<Seat> seats
-int totalCapacity
+getSeats() : List<Seat>
Movie
-String movieId
-String title
-int durationMinutes
-String genre
Show
-String showId
-Movie movie
-Screen screen
-LocalDateTime startTime
-Map<Seat-SeatStatus> seatAvailability
+getAvailableSeats() : List<Seat>
+lockSeats(List<Seat>) : boolean
+confirmSeats(List<Seat>)
+releaseSeats(List<Seat>)
Seat
-String seatId
-String row
-int number
-SeatType seatType
Booking
-String bookingId
-Show show
-List<Seat> seats
-BookingStatus status
-double totalAmount
+confirm(Payment) : boolean
+cancel() : boolean
+expire()
Payment
-String paymentId
-double amount
-PaymentStatus status
+process() : boolean
+refund()
«interface»
PricingStrategy
+calculatePrice(Seat, Show) : double
RegularPricing
+calculatePrice(Seat, Show) : double
WeekendPricing
-PricingStrategy baseStrategy
+calculatePrice(Seat, Show) : double
Key points to highlight in an interview: