25 min read · Capstone Complete System Design Lesson 12 of 12
Course 04 · Part 4 — Practical Engineering & Capstone

Mini Project: A Complete OOP System Design

From ambiguous requirements to executable code—synthesizing all OOP pillars in a capstone project

At the beginning of this course, we asserted: “OOP is not merely about typing classes and objects.” Throughout our journey, we investigated encapsulation, abstraction, inheritance, polymorphism, composition, and object collaboration in isolation. Now, the moment has arrived to unite every one of these pillars into an elegant, coherent, and executable Online Shopping Checkout System.

Capstone Milestone: Where Theory Becomes Architecture

In this capstone, we will not construct trivial textbook snippets. We will dissect a real-world business workflow, identify domain entities, establish state and invariants, and assemble an executable Python architecture that honors every core tenet of modern object-oriented software engineering.

01 Problem Statement & Candidate Concepts

Our e-commerce checkout engine has clear, unambiguous functional requirements:

1
Catalog & Cart: Customers browse available products and add items to a temporary shopping cart with designated quantities.
2
Subtotals & Checkout: The cart tallies dynamic totals, and the customer initiates checkout.
3
Payment & Invariant Locking: Charges execute through pluggable payment methods, locking in historical unit prices inside an Order.
4
Fulfillment & Alerts: The cart is flushed, the order status transitions to 'Paid', and confirmation receipts are dispatched.

These specifications directly map to our core domain relationships:

Customer → Cart → CartItem[] → Product
Order → OrderItem[] (Price Snapshot) → Product
CheckoutService → (Order, PaymentMethod, NotificationService)

02 Step-by-Step Domain Modeling & Code Construction

Let us construct each class systematically, ensuring clear demarcation of responsibilities:

Step 1: Product Class Core Domain Entity
class Product:
    def __init__(self, product_id: int, name: str, price: float, stock: int):
        self.product_id = product_id
        self.name = name
        self.price = price
        self.stock = stock

    def is_available(self, quantity: int = 1) -> bool:
        return self.stock >= quantity
Step 2: CartItem & Cart (Composition) Aggregate Container
class CartItem:
    def __init__(self, product: Product, quantity: int):
        self.product = product
        self.quantity = quantity

    def subtotal(self) -> float:
        return self.product.price * self.quantity

class Cart:
    def __init__(self):
        self.items: list[CartItem] = []

    def add_item(self, product: Product, quantity: int = 1):
        if not product.is_available(quantity):
            raise ValueError(f"Insufficient stock for {product.name} (Available: {product.stock})")
        self.items.append(CartItem(product, quantity))

    def remove_item(self, product_id: int):
        self.items = [item for item in self.items if item.product.product_id != product_id]

    def calculate_total(self) -> float:
        return sum(item.subtotal() for item in self.items)

    def clear(self):
        self.items.clear()
Step 3: Customer Class Customer & Cart Association
class Customer:
    def __init__(self, customer_id: int, name: str, email: str):
        self.customer_id = customer_id
        self.name = name
        self.email = email
        self.cart = Cart()  # Customer HAS-A Cart (Composition)
Step 4: OrderItem (Price Snapshot) & Order (State Machine) Invariant Protection & Encapsulation
class OrderItem:
    def __init__(self, product: Product, quantity: int):
        self.product = product
        self.quantity = quantity
        self.unit_price = product.price  # Permanent immutable snapshot of catalog price

    def subtotal(self) -> float:
        return self.unit_price * self.quantity

class Order:
    def __init__(self, order_id: int, customer: Customer, cart_items: list[CartItem]):
        self.order_id = order_id
        self.customer = customer
        self.items = [OrderItem(item.product, item.quantity) for item in cart_items]
        self.status = "Pending"

    def calculate_total(self) -> float:
        return sum(item.subtotal() for item in self.items)

    def mark_as_paid(self):
        if self.status != "Pending":
            raise ValueError(f"Invalid state transition: Cannot mark {self.status} order as Paid")
        self.status = "Paid"

    def complete(self):
        if self.status != "Paid":
            raise ValueError("Order must be Paid before it can be marked Completed")
        self.status = "Completed"

    def cancel(self):
        if self.status == "Completed":
            raise ValueError("Cannot cancel an already completed order")
        self.status = "Cancelled"
Step 5: PaymentMethod Interface & Polymorphic Handlers Abstraction & Polymorphism
from abc import ABC, abstractmethod

class PaymentMethod(ABC):
    @abstractmethod
    def pay(self, amount: float) -> bool:
        pass

class CardPayment(PaymentMethod):
    def pay(self, amount: float) -> bool:
        print(f"💳 [Card] Charged ${amount:.2f} via credit card successfully.")
        return True

class MobilePayment(PaymentMethod):
    def __init__(self, wallet_number: str):
        self.wallet_number = wallet_number

    def pay(self, amount: float) -> bool:
        print(f"📱 [Mobile Wallet] Charged ${amount:.2f} from wallet {self.wallet_number}.")
        return True
Step 6: NotificationService & CheckoutService (Orchestrator & DI) Collaboration & Dependency Injection
class NotificationService:
    def send_order_confirmation(self, order: Order):
        print(f"✉️ [Notification] Order #{order.order_id} confirmation dispatched to {order.customer.email}.")

class CheckoutService:
    def __init__(self, notification_service: NotificationService):
        # Injected dependency (Dependency Injection)
        self.notification_service = notification_service

    def checkout(self, customer: Customer, payment_method: PaymentMethod, order_id: int) -> Order:
        if not customer.cart.items:
            raise ValueError("Cannot checkout an empty cart!")

        # 1. Instantiate order with snapshotted items
        order = Order(order_id, customer, customer.cart.items)
        total = order.calculate_total()

        # 2. Polymorphic charge delegation
        if not payment_method.pay(total):
            raise ValueError("Payment transaction declined!")

        # 3. Transition order status & flush temporary cart
        order.mark_as_paid()
        customer.cart.clear()

        # 4. Dispatch notification receipt
        self.notification_service.send_order_confirmation(order)
        return order

03 System Simulation & Live Verification

Let us test the integrated pipeline end-to-end:

main_simulation.py
# 1. Instantiate Catalog Products & Customer
laptop = Product(product_id=1, name="ThinkPad Laptop", price=1200.0, stock=5)
mouse = Product(product_id=2, name="Logitech Mouse", price=25.0, stock=20)

customer = Customer(customer_id=101, name="Salim", email="salim@byteion.com")

# 2. Add merchandise to customer cart
customer.cart.add_item(laptop, quantity=1)
customer.cart.add_item(mouse, quantity=2)

print(f"🛒 Cart Total: ${customer.cart.calculate_total():.2f}")
# Output: $1250.00 (1200 + 25*2)

# 3. Assemble Checkout Engine with Injected Services
notifier = NotificationService()
checkout_engine = CheckoutService(notification_service=notifier)
mobile_pay = MobilePayment(wallet_number="+15550198")

# 4. Execute Checkout Workflow
confirmed_order = checkout_engine.checkout(customer, payment_method=mobile_pay, order_id=5001)

# 5. Verify Invariants
print(f"📦 Order Status: {confirmed_order.status}")
print(f"🛒 Remaining Cart Items: {len(customer.cart.items)}")

# 6. Complete Delivery Fulfillment
confirmed_order.complete()
print(f"🚚 Post-Delivery Status: {confirmed_order.status}")
Terminal Console Output:
🛒 Cart Total: $1250.00
📱 [Mobile Wallet] Charged $1250.00 from wallet +15550198.
✉️ [Notification] Order #5001 confirmation dispatched to salim@byteion.com.
📦 Order Status: Paid
🛒 Remaining Cart Items: 0
🚚 Post-Delivery Status: Completed

04 Mapping the Pillars to Real Architecture

Notice how every pillar we mastered across the curriculum fell organically into place:

OOP Concept Implementation in Capstone Architectural Value
Class & Object Product, Cart, Order, Customer blueprints & instances Expresses the business domain in coherent software models
Encapsulation Order state transitions via mark_as_paid() & complete() Shields financial state against invalid or out-of-order mutations
Abstraction PaymentMethod(ABC) interface Decouples checkout business logic from gateway payment mechanics
Inheritance CardPayment and MobilePayment subclassing PaymentMethod Guarantees adherence to the polymorphic payment contract
Polymorphism payment_method.pay(total) Allows adding new gateways without modifying the checkout engine
Composition Customer HAS-A Cart; Order HAS-A OrderItem[] Avoids fragile inheritance trees by assembling modular components
Collaboration & DI CheckoutService injecting NotificationService via constructor Enables loose coupling and seamless mocking during unit tests

05 Unified System Architecture Graph

A bird's-eye view of collaborating entities, component lifecycles, and message passing pathways:

                            Customer
                               │
                               ▼ (has a)
                              Cart
                               │
                               ▼ (contains)
                           CartItem[]
                               │
                               ▼ (references)
                            Product


Customer (initiates)
    │
    ▼
CheckoutService (Orchestrator)
    │
    ├───────────► Order (State Machine)
    │               │
    │               ▼ (contains)
    │           OrderItem[] (Price Snapshot)
    │
    ├───────────► PaymentMethod <<interface>>
    │                   │
    │             ┌─────┴─────┐
    │             ▼           ▼
    │        CardPayment  MobilePayment
    │
    └───────────► NotificationService
        

06 The Grand Retrospective: Our Journey Across Course 04

From our very first question to the final capstone, trace your intellectual evolution:

01. What is OOP & Why Do We Need It? → Escaping procedural spaghetti through object thinking
02. Classes & Objects → Understanding blueprints vs. live heap instances
03. State, Behavior & Methods → Identity, memory properties, and action capabilities
04. Encapsulation → Defending invariants and establishing sovereign boundaries
05. Abstraction → Hiding implementation details behind minimalist interfaces
06. Inheritance → Specialization and the perils of deep hierarchy
07. Polymorphism → Uniform interfaces manifesting specialized behaviors
08. Composition → Embracing "has-a" architectures to conquer class explosion
09. Relationships & Collaboration → Message passing, dependency, and object networks
10. Real-World Modeling → Translating business requirements into domain models
11. Anti-Patterns & When Not to Use OOP → Slaying God Objects and celebrating pure functions
12. Complete OOP System Design → Full-lifecycle capstone project implementation
Capstone Concept Check

1. In our checkout system, why does OrderItem store an explicit copy of `unit_price = product.price` instead of dynamically referencing `self.product.price`?

2. What architectural design pattern is demonstrated by passing `notification_service` into `CheckoutService.__init__()`?

Congratulations! Course 04 is Complete

You have successfully transitioned from basic language syntax to genuine Architectural Thinking. You now possess the insight to decompose messy real-world domains into elegant, maintainable object collaborations.

Up Next

Course 05 — Data Structures (Visual)

Transition from domain modeling into algorithmic efficiency and memory optimization: Arrays, Linked Lists, Stacks, Queues, Hash Tables, Trees, Heaps, and Graphs—brought to life with interactive visual representations!