UML Class Diagrams: Static Structure and Object-Oriented Design

1. Overview

A class diagram is a type of structural diagram in the Unified Modeling Language (UML) that represents the static structure of a system. It shows classes, their attributes, methods, and the relationships between them. Class diagrams are essential for documenting object-oriented (OOP) systems and serve as a bridge between design and implementation.

Class diagrams are the most widely used UML diagram type in software development. They answer fundamental questions:

Unlike sequence or activity diagrams that show behavior over time, class diagrams provide a static snapshot of the system's architecture. They are invaluable for:

2. Core Elements

2.1 Classes

A class in a class diagram is represented as a rectangle divided into three horizontal sections:

Example class structure:

┌─────────────────────────────┐
│        Customer             │  ← Class name (bold)
├─────────────────────────────┤
│ -customerId: int            │  ← Attributes (private)
│ -name: String               │
│ -email: String              │
│ #membershipLevel: int       │  ← Protected attribute
│ +isActive: boolean          │  ← Public attribute
├─────────────────────────────┤
│ +getOrders(): List<Order>   │  ← Methods (public)
│ -calculateDiscount(): float │  ← Private method
│ +updateEmail(newEmail)      │
└─────────────────────────────┘

2.2 Visibility Modifiers

Visibility determines which classes can access an attribute or method:

2.3 Abstract Classes and Interfaces

Abstract classes are displayed with italicized names and cannot be instantiated directly. They serve as base classes for concrete implementations.

Interfaces are marked with the <<interface>> stereotype. They define contracts that other classes must implement.

┌──────────────────────┐         ┌──────────────────────┐
│    <<interface>>    │         │   *PaymentMethod*   │
│    Drawable          │         │  (abstract class)    │
├──────────────────────┤         ├──────────────────────┤
│                      │         │ #amount: float       │
├──────────────────────┤         ├──────────────────────┤
│ +draw(): void        │         │ +processPayment()    │
│ +getArea(): float    │         │ +refund(): boolean   │
└──────────────────────┘         └──────────────────────┘

2.4 Enumerations

Enumerations (enums) are shown as classes with the <<enumeration>> stereotype, listing constant values instead of methods:

┌─────────────────────┐
│ <<enumeration>>    │
│   OrderStatus       │
├─────────────────────┤
│ PENDING             │
│ PROCESSING          │
│ SHIPPED             │
│ DELIVERED           │
│ CANCELLED           │
└─────────────────────┘

3. Relationships Between Classes

Class diagrams show five primary types of relationships. Each is represented by different line styles and symbols.

3.1 Association (Solid Line)

An association represents a structural relationship between two independent classes. It indicates that instances of one class are somehow related to instances of another. Associations are the weakest form of coupling.

┌──────────────┐         ┌──────────────┐
│   Teacher    │─────→   │   Student    │
├──────────────┤    1   *├──────────────┤
│ -name: str   │         │ -name: str   │
├──────────────┤         ├──────────────┤
│ +teach()     │         │ +study()     │
└──────────────┘         └──────────────┘
"teaches"          "taught by"

The numbers 1 and * represent multiplicity:

3.2 Aggregation (Empty Diamond)

An aggregation is a "has-a" relationship where the whole and part are loosely coupled. The part can exist independently of the whole. The diamond symbol appears on the side of the whole class.

┌────────────┐       ◇─────────  ┌────────────┐
│ Department │       │     1..*  │  Employee  │
├────────────┤       │           ├────────────┤
│ -name: str │───────┼───────────│ -name: str │
│ -budget    │       │           │ -salary    │
├────────────┤       └───────────├────────────┤
│ +hire()    │                   │ +work()    │
└────────────┘                   └────────────┘

A Department HAS-A collection of Employees.
Employees can exist without the Department.

3.3 Composition (Filled Diamond)

A composition is a strong "has-a" relationship where the whole owns the part. The part cannot exist without the whole. When the whole is deleted, parts are also deleted. The filled diamond indicates ownership.

┌─────────────┐       ◆─────────  ┌──────────┐
│   House     │       │     1..*  │  Room    │
├─────────────┤       │           ├──────────┤
│ -address    │───────┼───────────│ -name    │
│ -owner      │       │           │ -area    │
├─────────────┤       └───────────├──────────┤
│ +renovate() │                   │ +clean() │
└─────────────┘                   └──────────┘

A House is COMPOSED OF Rooms.
Rooms cannot exist without the House.

3.4 Inheritance/Generalization (Empty Triangle)

Inheritance (also called generalization) represents an "is-a" relationship. A subclass inherits attributes and methods from a superclass. The arrow points from the subclass to the superclass.

        ┌──────────────────┐
        │      *Animal*    │ (abstract base)
        ├──────────────────┤
        │ #name: str       │
        │ #age: int        │
        ├──────────────────┤
        │ +eat(): void     │
        │ +move(): void    │
        └────────┬─────────┘
                 │
        ┌────────┼────────┐
        │        │        │
        △        △        △
        │        │        │
   ┌────┴──┐ ┌──┴────┐ ┌──┴────┐
   │  Dog  │ │  Cat  │ │ Bird  │
   │───────│ │───────│ │───────│
   │ bark()│ │ meow()│ │ fly() │
   └───────┘ └───────┘ └───────┘

All are instances of Animal.

3.5 Realization/Implementation (Dashed Triangle)

Realization shows that a class implements an interface. The dashed line arrow points from the implementing class to the interface.

     ┌──────────────────────┐
     │  <<interface>>      │
     │   Comparable         │
     ├──────────────────────┤
     │ +compareTo(other)    │
     └────────────┬─────────┘
                  │
                  │ (dashed arrow, points upward)
                  │
     ┌────────────┴─────────┐
     │      Person          │
     ├──────────────────────┤
     │ -name: str           │
     │ -age: int            │
     ├──────────────────────┤
     │ +compareTo(other)    │
     └──────────────────────┘

Person realizes the Comparable interface.

3.6 Dependency (Dashed Arrow)

A dependency is a weak relationship where one class uses or depends upon another class, but they are not structurally connected. It typically represents a method parameter, local variable, or return type.

┌──────────────┐          ┌──────────────┐
│   Report     │ ⋯⋯⋯→    │  Database    │
├──────────────┤  uses    ├──────────────┤
│ -title: str  │          │ +query()     │
├──────────────┤          ├──────────────┤
│ +generate()  │          │ +save()      │
│ -fetchData() │ (depends)│              │
└──────────────┘          └──────────────┘

Report depends on Database to fetch data.

4. Example: E-Commerce System

4.1 Class Diagram (ASCII Art)

┌─────────────────────────────────┐
│        *ShippingAddress*        │
├─────────────────────────────────┤
│ -street: str                    │
│ -city: str                      │
│ -zipCode: str                   │
│ -country: str                   │
├─────────────────────────────────┤
│ +isValid(): boolean             │
└──────────────┬──────────────────┘
               │
               │ (composition 1)
               │
┌──────────────┴───────────────────┐
│         Customer                  │
├───────────────────────────────────┤
│ -customerId: int                  │
│ -name: str                        │
│ -email: str                       │
│ #membershipLevel: int             │
├───────────────────────────────────┤
│ +getOrders(): List<Order>        │
│ +calculateDiscount(): float       │
└──────┬──────────────────┬─────────┘
       │                  │
       │ (1 to *)         │ (1 to *)
       │ has              │ uses
       │                  │
   ┌───┴─────────┐   ┌────┴──────────────┐
   │    Order    │   │  PaymentMethod    │
   │             │   │  (abstract)       │
   ├─────────────┤   ├───────────────────┤
   │ -orderId    │   │ #amount: float    │
   │ -date       │   │ #status: str      │
   │ -total      │   ├───────────────────┤
   ├─────────────┤   │ +process(): bool  │
   │+calculate() │   │ +refund(): bool   │
   └──────┬──────┘   └─────────┬─────────┘
          │                    │
          │ (1 to *)           │
          │ contains           │ ▲
          │                    │ │ (realization)
      ┌───┴──────────────┐     │
      │   OrderItem      │     │
      ├──────────────────┤     │
      │ -itemId: int     │     │
      │ -quantity: int   │  ┌──┴──────────────┐
      │ -price: float    │  │ CreditCardPay   │
      ├──────────────────┤  │                 │
      │ +getSubtotal()   │  │ -cardNumber: str│
      └──────┬───────────┘  └─────────────────┘
             │
             │ (many to 1)
             │ references
             │
        ┌────┴─────────────────┐
        │      Product          │
        ├───────────────────────┤
        │ -productId: int       │
        │ -name: str            │
        │ -price: float         │
        │ -stock: int           │
        │ -category: str        │
        ├───────────────────────┤
        │ +updateStock(): void  │
        │ +isAvailable(): bool  │
        └───────────────────────┘

4.2 Python Implementation

from dataclasses import dataclass
from typing import List, Optional
from datetime import datetime
from enum import Enum
from abc import ABC, abstractmethod

class PaymentStatus(Enum):
    PENDING = "pending"
    COMPLETED = "completed"
    FAILED = "failed"
    REFUNDED = "refunded"

@dataclass
class ShippingAddress:
    """Represents a shipping address (composition within Customer)."""
    street: str
    city: str
    zip_code: str
    country: str

    def is_valid(self) -> bool:
        """Validate address format."""
        return all([self.street, self.city, self.zip_code, self.country])

@dataclass
class Product:
    """Represents a product in the e-commerce system."""
    product_id: int
    name: str
    price: float
    stock: int
    category: str

    def update_stock(self, quantity: int) -> None:
        """Reduce stock when product is ordered."""
        self.stock = max(0, self.stock - quantity)

    def is_available(self, quantity: int = 1) -> bool:
        """Check if product is available in requested quantity."""
        return self.stock >= quantity

class PaymentMethod(ABC):
    """Abstract base class for payment methods."""

    def __init__(self, amount: float):
        self.amount = amount
        self.status = PaymentStatus.PENDING

    @abstractmethod
    def process(self) -> bool:
        """Process the payment."""
        pass

    @abstractmethod
    def refund(self) -> bool:
        """Refund the payment."""
        pass

class CreditCardPayment(PaymentMethod):
    """Concrete implementation of credit card payment."""

    def __init__(self, amount: float, card_number: str):
        super().__init__(amount)
        self.card_number = card_number

    def process(self) -> bool:
        """Process credit card payment."""
        # Simulate card processing
        self.status = PaymentStatus.COMPLETED
        return True

    def refund(self) -> bool:
        """Refund credit card payment."""
        if self.status == PaymentStatus.COMPLETED:
            self.status = PaymentStatus.REFUNDED
            return True
        return False

@dataclass
class OrderItem:
    """Represents a line item in an order."""
    item_id: int
    product: Product
    quantity: int
    price: float

    def get_subtotal(self) -> float:
        """Calculate subtotal for this item."""
        return self.price * self.quantity

@dataclass
class Order:
    """Represents a customer order."""
    order_id: int
    customer_id: int
    date: datetime
    items: List[OrderItem]
    shipping_address: ShippingAddress
    payment: Optional[PaymentMethod] = None
    total: float = 0.0

    def calculate(self) -> float:
        """Calculate total order amount."""
        self.total = sum(item.get_subtotal() for item in self.items)
        return self.total

    def apply_discount(self, discount_percent: float) -> float:
        """Apply discount to order total."""
        discount_amount = self.total * (discount_percent / 100)
        return self.total - discount_amount

@dataclass
class Customer:
    """Represents a customer (composition with ShippingAddress)."""
    customer_id: int
    name: str
    email: str
    membership_level: int = 0
    is_active: bool = True
    orders: List[Order] = None
    shipping_address: ShippingAddress = None

    def __post_init__(self):
        if self.orders is None:
            self.orders = []

    def get_orders(self) -> List[Order]:
        """Retrieve all orders for this customer."""
        return self.orders

    def calculate_discount(self) -> float:
        """Calculate loyalty discount based on membership level."""
        discounts = {0: 0.0, 1: 0.05, 2: 0.10, 3: 0.15}
        return discounts.get(self.membership_level, 0.0)

    def place_order(self, order: Order) -> bool:
        """Place a new order for this customer."""
        if self.is_active and order.shipping_address.is_valid():
            self.orders.append(order)
            return True
        return False

# Example usage
if __name__ == "__main__":
    # Create a customer with shipping address
    address = ShippingAddress(
        street="123 Main St",
        city="Portland",
        zip_code="97201",
        country="USA"
    )

    customer = Customer(
        customer_id=1,
        name="Alice Johnson",
        email="alice@example.com",
        membership_level=2,
        shipping_address=address
    )

    # Create products
    product1 = Product(1, "Laptop", 999.99, 10, "Electronics")
    product2 = Product(2, "Mouse", 29.99, 50, "Accessories")

    # Create order items
    item1 = OrderItem(1, product1, 1, 999.99)
    item2 = OrderItem(2, product2, 2, 29.99)

    # Create order
    order = Order(
        order_id=101,
        customer_id=1,
        date=datetime.now(),
        items=[item1, item2],
        shipping_address=address
    )

    # Calculate total and apply discount
    total = order.calculate()
    discount = customer.calculate_discount()
    final_total = order.apply_discount(discount * 100)

    # Process payment
    payment = CreditCardPayment(final_total, "4111111111111111")
    payment.process()
    order.payment = payment

    print(f"Order Total: ${total:.2f}")
    print(f"Discount: {discount*100}%")
    print(f"Final Total: ${final_total:.2f}")
    print(f"Payment Status: {payment.status.value}")

5. Example: Design Patterns in Class Diagrams

5.1 Strategy Pattern

The Strategy pattern defines a family of algorithms, encapsulates each one, and makes them interchangeable.

┌────────────────────────┐
│ <<interface>>         │
│   PaymentStrategy      │
├────────────────────────┤
│ +pay(amount): boolean  │
└────────┬───────────────┘
         │
         │ (realization)
         │
    ┌────┼────────────────────┐
    │    │                    │
   ┌┴────┴─────┐   ┌──────────┴────┐   ┌─────────────┐
   │ CardPay   │   │ PayPalPay     │   │ BitcoinPay  │
   ├───────────┤   ├───────────────┤   ├─────────────┤
   │ +pay()    │   │ +pay()        │   │ +pay()      │
   └───────────┘   └───────────────┘   └─────────────┘

┌──────────────────┐
│ PaymentProcessor │
├──────────────────┤
│ -strategy        │ ⋯⋯⋯→ (depends on PaymentStrategy)
├──────────────────┤
│ +execute()       │
└──────────────────┘

5.2 Observer Pattern

The Observer pattern defines a one-to-many dependency where when one object changes state, all dependents are notified automatically.

┌─────────────────────┐         ┌──────────────────────┐
│ <<interface>>      │         │     Subject          │
│   Observer          │         ├──────────────────────┤
├─────────────────────┤         │ -observers: List     │
│ +update(): void     │         ├──────────────────────┤
└────────┬────────────┘         │ +attach(observer)    │
         │ (realization)        │ +detach(observer)    │
         │                      │ +notify()            │
    ┌────┴──────────┬───────┐   └──────────────────────┘
    │               │       │
    │       ◇───────┴───────┘
    │       │ (aggregation 1..*: notifies)
    │       │
   ┌┴──────────┐  ┌────────────┐  ┌────────────┐
   │ EmailSub  │  │ SMSSub     │  │ LogSub     │
   │           │  │            │  │            │
   │ +update() │  │ +update()  │  │ +update()  │
   └───────────┘  └────────────┘  └────────────┘

5.3 Factory Pattern

The Factory pattern provides an interface for creating objects without specifying their concrete classes.

┌────────────────────────┐
│   <<abstract>>       │
│   DocumentFactory      │
├────────────────────────┤
│ +createDocument()      │
└────────┬───────────────┘
         │
         │ (generalization)
         │
    ┌────┼──────────────┐
    │    │              │
   ┌┴────┴──────┐  ┌────┴──────────┐  ┌──────────────┐
   │ PDFFactory │  │ WordFactory    │  │ ExcelFactory │
   ├────────────┤  ├────────────────┤  ├──────────────┤
   │ +create()  │  │ +create()      │  │ +create()    │
   └────┬───────┘  └───────┬────────┘  └────────┬─────┘
        │                  │                   │
        │ returns          │ returns           │ returns
        │                  │                   │
   ┌────┴──────┐      ┌────┴────────┐    ┌────┴─────┐
   │ PDFDoc    │      │ WordDoc      │    │ ExcelDoc  │
   ├───────────┤      ├─────────────┤    ├──────────┤
   │ +open()   │      │ +open()     │    │ +open()  │
   │ +save()   │      │ +save()     │    │ +save()  │
   └───────────┘      └─────────────┘    └──────────┘

5.4 Design Pattern Python Implementation

# Strategy Pattern
from abc import ABC, abstractmethod

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

class CardPayment(PaymentStrategy):
    def pay(self, amount: float) -> bool:
        print(f"Processing card payment of ${amount}")
        return True

class PayPalPayment(PaymentStrategy):
    def pay(self, amount: float) -> bool:
        print(f"Processing PayPal payment of ${amount}")
        return True

class PaymentProcessor:
    def __init__(self, strategy: PaymentStrategy):
        self.strategy = strategy

    def execute_payment(self, amount: float) -> bool:
        return self.strategy.pay(amount)

# Observer Pattern
class Observer(ABC):
    @abstractmethod
    def update(self, message: str) -> None:
        pass

class Subject:
    def __init__(self):
        self._observers: list = []

    def attach(self, observer: Observer) -> None:
        self._observers.append(observer)

    def detach(self, observer: Observer) -> None:
        self._observers.remove(observer)

    def notify(self, message: str) -> None:
        for observer in self._observers:
            observer.update(message)

class EmailSubscriber(Observer):
    def update(self, message: str) -> None:
        print(f"Email sent: {message}")

class SMSSubscriber(Observer):
    def update(self, message: str) -> None:
        print(f"SMS sent: {message}")

# Factory Pattern
class DocumentFactory(ABC):
    @abstractmethod
    def create_document(self):
        pass

class PDFFactory(DocumentFactory):
    def create_document(self):
        return PDFDocument()

class WordFactory(DocumentFactory):
    def create_document(self):
        return WordDocument()

class PDFDocument:
    def save(self, filename: str) -> None:
        print(f"Saving PDF: {filename}")

class WordDocument:
    def save(self, filename: str) -> None:
        print(f"Saving Word: {filename}")

6. Example: REST API Models

A typical REST API involves models for authentication, authorization, and data transfer.

┌──────────────────────┐
│    <<abstract>>      │
│   APIResponse<T>     │
├──────────────────────┤
│ -statusCode: int     │
│ -message: str        │
│ -data: T             │
├──────────────────────┤
│ +toJson(): str       │
└────────┬─────────────┘
         │ (generalization)
         │
    ┌────┼──────────────────────────┐
    │    │                          │
   ┌┴────┴────────────┐  ┌──────────┴────────┐
   │ SuccessResponse  │  │ ErrorResponse     │
   ├──────────────────┤  ├───────────────────┤
   │ -timestamp       │  │ -errors: List     │
   ├──────────────────┤  ├───────────────────┤
   └──────────────────┘  └───────────────────┘

┌─────────────────────┐         ┌───────────────┐
│      Token          │ ◆────→  │  User         │
├─────────────────────┤ 1    *  ├───────────────┤
│ -accessToken: str   │         │ -userId: int  │
│ -tokenType: str     │         │ -username: str│
│ -expiresIn: int     │         │ -email: str   │
├─────────────────────┤         ├───────────────┤
│ +isValid(): bool    │         │ +getRole()    │
│ +refresh(): Token   │         └──────┬────────┘
└─────────────────────┘                │
                                       │ has (1 to *)
                                       │
                                   ┌───┴──────┐
                                   │   Role   │
                                   ├──────────┤
                                   │ -roleId  │
                                   │ -name    │
                                   ├──────────┤
                                   │ perms(): │
                                   └──┬───────┘
                                      │
                                      │ has (1 to *)
                                      │
                                   ┌──┴─────────┐
                                   │ Permission │
                                   ├────────────┤
                                   │ -permId    │
                                   │ -resource  │
                                   │ -action    │
                                   └────────────┘

7. Mermaid.js Syntax

Mermaid is a JavaScript-based diagramming and charting tool that renders markdown-inspired syntax. Below is the Mermaid classDiagram syntax for the e-commerce system:

classDiagram
    class Customer {
        -int customerId
        -string name
        -string email
        #int membershipLevel
        +List~Order~ getOrders()
        +float calculateDiscount()
    }

    class Order {
        -int orderId
        -datetime date
        -float total
        +float calculate()
    }

    class OrderItem {
        -int itemId
        -int quantity
        -float price
        +float getSubtotal()
    }

    class Product {
        -int productId
        -string name
        -float price
        -int stock
        +void updateStock()
        +bool isAvailable()
    }

    class PaymentMethod {
        <<abstract>>
        #float amount
        #string status
        +bool process()
        +bool refund()
    }

    class CreditCardPayment {
        -string cardNumber
        +bool process()
        +bool refund()
    }

    class ShippingAddress {
        -string street
        -string city
        -string zipCode
        -string country
        +bool isValid()
    }

    Customer "1" --> "*" Order : places
    Customer "1" -- "1" ShippingAddress : has
    Order "1" --> "*" OrderItem : contains
    OrderItem "*" --> "1" Product : references
    Order "*" --> "1" PaymentMethod : uses
    CreditCardPayment --|> PaymentMethod : implements

8. From Diagram to Code

Class diagrams translate directly into code. Here's how the mapping works:

8.1 Mapping Diagram Elements to Code

8.2 Full E-Commerce Example with Dataclasses

from dataclasses import dataclass, field
from typing import List, Optional
from datetime import datetime
from enum import Enum
from abc import ABC, abstractmethod

class OrderStatus(Enum):
    PENDING = "pending"
    CONFIRMED = "confirmed"
    SHIPPED = "shipped"
    DELIVERED = "delivered"
    CANCELLED = "cancelled"

@dataclass
class ShippingAddress:
    """Represents a physical shipping address."""
    street: str
    city: str
    zip_code: str
    country: str

    def is_valid(self) -> bool:
        """Validate that all required fields are present."""
        return bool(self.street and self.city and self.zip_code and self.country)

    def format_address(self) -> str:
        """Format address for display."""
        return f"{self.street}\n{self.city}, {self.zip_code}\n{self.country}"

@dataclass
class Product:
    """Represents a product available for purchase."""
    product_id: int
    name: str
    price: float
    stock: int
    category: str
    description: str = ""

    def update_stock(self, quantity: int) -> None:
        """Reduce stock when item is ordered."""
        self.stock = max(0, self.stock - quantity)

    def is_available(self, quantity: int = 1) -> bool:
        """Check if product can be ordered."""
        return self.stock >= quantity

    def get_price(self) -> float:
        """Return current product price."""
        return self.price

@dataclass
class OrderItem:
    """Represents a single line item in an order."""
    item_id: int
    product: Product
    quantity: int
    unit_price: float

    def get_subtotal(self) -> float:
        """Calculate line item total."""
        return self.unit_price * self.quantity

    def get_product_name(self) -> str:
        """Get the name of the ordered product."""
        return self.product.name

class PaymentMethod(ABC):
    """Abstract base class for all payment methods."""

    def __init__(self, amount: float):
        self.amount = amount
        self.status = "PENDING"

    @abstractmethod
    def process(self) -> bool:
        """Process the payment. Must be implemented by subclasses."""
        pass

    @abstractmethod
    def refund(self) -> bool:
        """Refund the payment. Must be implemented by subclasses."""
        pass

class CreditCardPayment(PaymentMethod):
    """Payment via credit or debit card."""

    def __init__(self, amount: float, card_number: str, cvv: str):
        super().__init__(amount)
        self.card_number = card_number
        self.cvv = cvv

    def process(self) -> bool:
        """Process credit card payment."""
        # Validate card
        if not self._validate_card():
            self.status = "FAILED"
            return False

        # Simulate payment processing
        self.status = "COMPLETED"
        return True

    def refund(self) -> bool:
        """Refund the credit card payment."""
        if self.status == "COMPLETED":
            self.status = "REFUNDED"
            return True
        return False

    def _validate_card(self) -> bool:
        """Validate card format."""
        return len(self.card_number) == 16 and len(self.cvv) == 3

class PayPalPayment(PaymentMethod):
    """Payment via PayPal."""

    def __init__(self, amount: float, email: str):
        super().__init__(amount)
        self.email = email

    def process(self) -> bool:
        """Process PayPal payment."""
        self.status = "COMPLETED"
        return True

    def refund(self) -> bool:
        """Refund PayPal payment."""
        if self.status == "COMPLETED":
            self.status = "REFUNDED"
            return True
        return False

@dataclass
class Order:
    """Represents a customer order."""
    order_id: int
    customer_id: int
    date: datetime
    status: OrderStatus = OrderStatus.PENDING
    items: List[OrderItem] = field(default_factory=list)
    shipping_address: Optional[ShippingAddress] = None
    payment: Optional[PaymentMethod] = None
    total: float = 0.0
    discount_percent: float = 0.0

    def add_item(self, item: OrderItem) -> None:
        """Add an item to the order."""
        self.items.append(item)

    def calculate(self) -> float:
        """Calculate order total before discount."""
        self.total = sum(item.get_subtotal() for item in self.items)
        return self.total

    def apply_discount(self) -> float:
        """Apply discount and return final total."""
        if self.total == 0:
            self.calculate()
        discount_amount = self.total * (self.discount_percent / 100)
        return self.total - discount_amount

    def finalize_payment(self, payment: PaymentMethod) -> bool:
        """Attach payment and process it."""
        self.payment = payment
        if payment.process():
            self.status = OrderStatus.CONFIRMED
            return True
        return False

    def get_summary(self) -> dict:
        """Get order summary."""
        final_total = self.apply_discount()
        return {
            "order_id": self.order_id,
            "item_count": len(self.items),
            "subtotal": self.total,
            "discount": self.discount_percent,
            "final_total": final_total,
            "status": self.status.value
        }

@dataclass
class Customer:
    """Represents a customer in the e-commerce system."""
    customer_id: int
    name: str
    email: str
    membership_level: int = 0
    is_active: bool = True
    orders: List[Order] = field(default_factory=list)
    shipping_address: Optional[ShippingAddress] = None

    def get_orders(self) -> List[Order]:
        """Retrieve all orders for this customer."""
        return self.orders

    def place_order(self, order: Order) -> bool:
        """Place a new order if customer is active and address is valid."""
        if not self.is_active:
            return False

        if self.shipping_address and not self.shipping_address.is_valid():
            return False

        order.customer_id = self.customer_id
        order.shipping_address = self.shipping_address
        self.orders.append(order)
        return True

    def calculate_discount(self) -> float:
        """Calculate loyalty discount based on membership level."""
        membership_discounts = {
            0: 0.0,    # Standard customer: 0%
            1: 0.05,   # Silver: 5%
            2: 0.10,   # Gold: 10%
            3: 0.15    # Platinum: 15%
        }
        return membership_discounts.get(self.membership_level, 0.0)

    def upgrade_membership(self) -> None:
        """Upgrade customer to next membership level."""
        if self.membership_level < 3:
            self.membership_level += 1

    def deactivate(self) -> None:
        """Deactivate customer account."""
        self.is_active = False

# Example usage demonstrating class relationships
if __name__ == "__main__":
    # Create shipping address
    address = ShippingAddress(
        street="456 Oak Avenue",
        city="Seattle",
        zip_code="98101",
        country="USA"
    )

    # Create customer
    customer = Customer(
        customer_id=101,
        name="Jane Smith",
        email="jane.smith@example.com",
        membership_level=2,  # Gold member
        shipping_address=address
    )

    # Create products
    laptop = Product(1, "ThinkPad X1", 1299.99, 5, "Electronics", "Professional laptop")
    monitor = Product(2, "4K Monitor", 599.99, 12, "Electronics", "Ultra HD display")

    # Create order items
    item1 = OrderItem(1, laptop, 1, 1299.99)
    item2 = OrderItem(2, monitor, 1, 599.99)

    # Create order
    order = Order(
        order_id=1001,
        customer_id=customer.customer_id,
        date=datetime.now(),
        items=[item1, item2]
    )

    # Set discount for Gold member
    order.discount_percent = customer.calculate_discount() * 100

    # Place order
    if customer.place_order(order):
        print("Order placed successfully!")

    # Calculate totals
    subtotal = order.calculate()
    final_total = order.apply_discount()

    print(f"\n--- Order Summary ---")
    print(f"Order ID: {order.order_id}")
    print(f"Items: {len(order.items)}")
    print(f"Subtotal: ${subtotal:.2f}")
    print(f"Discount: {customer.calculate_discount()*100}%")
    print(f"Final Total: ${final_total:.2f}")

    # Process payment
    payment = CreditCardPayment(final_total, "4111111111111111", "123")
    if order.finalize_payment(payment):
        print(f"Payment Status: {payment.status}")
        print(f"Order Status: {order.status.value}")

9. Best Practices for Class Diagrams

10. Common Mistakes to Avoid

Conclusion

Class diagrams are the blueprint for object-oriented software. They bridge the gap between requirements and code, enabling teams to communicate design decisions clearly. Mastering class diagram notation—understanding relationships, multiplicities, and visibility—is essential for software architects and developers.

Start with simple diagrams, focus on key relationships, and always align your diagrams with actual code. Used effectively, class diagrams become invaluable documentation that prevents misunderstandings and guides implementation.