Card 04/ 08

CodeDifficulty: Intermediate3 min

From Class Diagram to Code in Python, Java and TypeScript

The same boxes and arrows, written three ways.

Each arrow in a class diagram has a usual form in code, and writing one diagram in three languages shows which parts are the notation and which are the language. This card takes a small order system with five of the six relationships and writes it in Python, Java and TypeScript, with each relationship's line highlighted.

An order system with five relationships

The order system in three languages

py
from typing import Protocol


class Notifier(Protocol):
    def send(self, text: str) -> None: ...


class EmailNotifier(Notifier):
    def send(self, text: str) -> None:
        print(f"email: {text}")


class OrderLine:
    def __init__(self, name: str, price: float) -> None:
        self.name = name
        self.price = price


class Order:
    def __init__(self) -> None:
        self._lines: list[OrderLine] = []

    def add_line(self, name: str, price: float) -> None:
        self._lines.append(OrderLine(name, price))

    def total(self) -> float:
        return sum(line.price for line in self._lines)


class PriorityOrder(Order):
    def total(self) -> float:
        return super().total() + 5.0


class Checkout:
    def __init__(self, notifier: Notifier) -> None:
        self._notifier = notifier

    def place(self, order: Order) -> None:
        self._notifier.send(f"order placed, total {order.total():.2f}")


order = PriorityOrder()
order.add_line("book", 12.5)
order.add_line("pen", 2.5)
Checkout(EmailNotifier()).place(order)
OutputRan with python:3.12-slim (Python 3.12.13)
email: order placed, total 20.00
Python names the protocol as a base class to make the realisation visible; a matching class would satisfy it without that.
java
import java.util.ArrayList;
import java.util.List;

public class UmlToCode {
    public static void main(String[] args) {
        Order order = new PriorityOrder();
        order.addLine("book", 12.5);
        order.addLine("pen", 2.5);
        new Checkout(new EmailNotifier()).place(order);
    }
}

interface Notifier {
    void send(String text);
}

class EmailNotifier implements Notifier {
    public void send(String text) {
        System.out.println("email: " + text);
    }
}

class OrderLine {
    final String name;
    final double price;

    OrderLine(String name, double price) {
        this.name = name;
        this.price = price;
    }
}

class Order {
    private final List<OrderLine> lines = new ArrayList<>();

    void addLine(String name, double price) {
        lines.add(new OrderLine(name, price));
    }

    double total() {
        return lines.stream().mapToDouble(line -> line.price).sum();
    }
}

class PriorityOrder extends Order {
    @Override
    double total() {
        return super.total() + 5.0;
    }
}

class Checkout {
    private final Notifier notifier;

    Checkout(Notifier notifier) {
        this.notifier = notifier;
    }

    void place(Order order) {
        notifier.send(String.format("order placed, total %.2f", order.total()));
    }
}
OutputRan with eclipse-temurin:21-jdk (Java 21.0.12)
email: order placed, total 20.00
Java's package-private members, with no keyword, are the `~` mark.
ts
interface Notifier {
  send(text: string): void;
}

class EmailNotifier implements Notifier {
  send(text: string): void {
    console.log(`email: ${text}`);
  }
}

class OrderLine {
  constructor(readonly name: string, readonly price: number) {}
}

class Order {
  private readonly lines: OrderLine[] = [];

  addLine(name: string, price: number): void {
    this.lines.push(new OrderLine(name, price));
  }

  total(): number {
    return this.lines.reduce((sum, line) => sum + line.price, 0);
  }
}

class PriorityOrder extends Order {
  total(): number {
    return super.total() + 5.0;
  }
}

class Checkout {
  constructor(private readonly notifier: Notifier) {}

  place(order: Order): void {
    this.notifier.send(`order placed, total ${order.total().toFixed(2)}`);
  }
}

const order = new PriorityOrder();
order.addLine("book", 12.5);
order.addLine("pen", 2.5);
new Checkout(new EmailNotifier()).place(order);
OutputRan with node:22-slim (Node 22.23.1), TypeScript 5.9.3
email: order placed, total 20.00
TypeScript's `private readonly` constructor parameter declares the association's field and fills it in one line.

The highlighted lines are the arrows, in the same order in each language:

  • Realisation is the implementing declaration: class EmailNotifier(Notifier) in Python, implements Notifier in Java and TypeScript.
  • Composition is Order creating each OrderLine itself, inside add_line. No other object ever holds a line, so the lines go when the order does.
  • Inheritance is PriorityOrder naming Order as its parent and overriding total.
  • Association is the notifier field, set once in the constructor and kept.
  • Dependency is the order parameter of place. Checkout uses the order for one call and keeps no reference to it.

The diagram does not say how a language spells privacy. Java and TypeScript enforce private; Python marks it with a leading underscore and trusts the reader. All three print the same line, because the structure is the same.

Aggregation is the one relationship missing. It would look like composition with one change: the parts arrive as parameters from outside instead of being created inside, so they can outlive the whole.