In the world of software development, class diagrams are more than just static illustrations ā they are living blueprints that evolve alongside the system they represent. At every stage of development, from initial requirements to post-release maintenance, the level of detail, structure, and intent behind a class diagram shifts dramatically. Yet, one common pitfall persists: isolated components.
Consider the typical payment processor class ā CreditCardProcessor, PayPalProcessor, and StripeProcessor ā often modeled as standalone, disconnected entities in a class diagram. While this may suffice during early design, it reveals a deeper issue: a lack of integration and behavioral clarity. These classes exist in isolation, with no clear mechanism for selection, configuration, or runtime flexibility. As a result, the design becomes rigid, hard to extend, and difficult to test.
This article explores how class diagrams should evolve across development stages ā from high-level conceptual models to detailed, implementation-ready designs ā and how strategic connections between components can transform a fragmented system into a cohesive, scalable architecture. Weāll focus on a real-world example: the payment processing subsystem ā and show how applying the Strategy Pattern, Factory Pattern, and dependency injection can bridge the gap between isolated classes and a truly dynamic, maintainable system.
Through PlantUML diagrams and practical design insights, youāll learn how to:
By the end, youāll see that a well-connected class diagram isnāt just a documentation tool ā itās a vision of how your software should work.
Class diagrams are one of the most powerful UML tools for modeling object-oriented systems. TheirĀ level of detailĀ changes significantly depending on theĀ development stage. This guide walks you throughĀ four key stagesĀ of software development and shows how class diagrams evolve accordingly.
Capture high-level domain concepts.
Identify key entities and their relationships.
Facilitate communication between stakeholders and developers.
Focus onĀ domain entitiesĀ andĀ relationships.
No methods or attributes (or minimal).
UseĀ generalization,Ā association,Ā aggregation, andĀ composition.
Avoid implementation details (e.g., access modifiers, data types).
@startuml
' Conceptual Class Diagram - Stage 1: Requirements
class Customer {
+name: String
+email: String
}
class Product {
+name: String
+price: Decimal
}
class Order {
+orderDate: Date
+status: String
}
Customer "1" -- "0..*" Order : places
Order "1" -- "1..*" Product : contains
Product "1" -- "0..*" Order : sold in
note right of Customer
Represents a user buying products
end note
note right of Product
Physical or digital item for sale
end note
note right of Order
A transaction record
end note
@enduml
ā Ā Use Case: Present to stakeholders, refine domain model, validate with business analysts.
Refine domain model into a more structured design.
IntroduceĀ attributes,Ā basic operations, andĀ associations.
Begin to identifyĀ interfaces,Ā abstract classes, andĀ design patterns.
AddĀ attributesĀ andĀ operationsĀ (with minimal types).
UseĀ abstract classesĀ andĀ interfaces.
IntroduceĀ multiplicityĀ andĀ navigability.
Start thinking aboutĀ responsibilitiesĀ andĀ cohesion.
@startuml
' High-Level Class Diagram - Stage 2: Analysis
@startuml
' High-Level Class Diagram - Stage 2: Analysis
abstract class Order {
- orderID: String
- orderDate: Date
- status: String
+calculateTotal(): Decimal
+validate(): Boolean
+save(): void
}
class Customer {
- customerID: String
- name: String
- email: String
+addOrder(order: Order): void
+getOrders(): List<Order>
}
class Product {
- productID: String
- name: String
- price: Decimal
- stockQuantity: Integer
+isInStock(): Boolean
+updateStock(amount: Integer): void
}
class OrderItem {
- quantity: Integer
- unitPrice: Decimal
+getSubtotal(): Decimal
}
Customer "1" -- "0..*" Order : places
Order "1" -- "1..*" OrderItem : contains
OrderItem "1" -- "1" Product : references
Product "1" -- "0..*" OrderItem : appears in
interface PaymentProcessor {
+processPayment(amount: Decimal): Boolean
}
Order "1" -- "1" PaymentProcessor : uses
@enduml
ā Ā Use Case: Design review, team alignment, initial architecture decisions.
Prepare for coding.
DefineĀ exact attributes,Ā methods,Ā data types,Ā access modifiers.
IncludeĀ constraints,Ā dependencies,Ā associations, andĀ composition.
UseĀ design patternsĀ (e.g., Factory, Strategy, Singleton).
Full method signatures and return types.
Use ofĀ access modifiersĀ (+,Ā -,Ā #).
Dependencies,Ā inheritance,Ā interfacesĀ are fully specified.
May includeĀ constraintsĀ (e.g.,Ā <<constraint>>).
@startuml
' Detailed Class Diagram - Stage 3: Implementation
@startuml
' Detailed Class Diagram - Stage 3: Implementation
class Customer {
- customerID: String
- name: String
- email: String
- address: String
+addOrder(order: Order): void
+getOrders(): List<Order>
+validateEmail(): Boolean
}
class Order {
- orderID: String
- orderDate: Date
- status: OrderStatus
- total: Decimal
+calculateTotal(): Decimal
+validate(): Boolean
+save(): void
+cancel(): void
}
class OrderItem {
- quantity: Integer
- unitPrice: Decimal
+getSubtotal(): Decimal
}
class Product {
- productID: String
- name: String
- price: Decimal
- stockQuantity: Integer
+isInStock(): Boolean
+updateStock(amount: Integer): void
+getPrice(): Decimal
}
class PaymentProcessor {
+processPayment(amount: Decimal): Boolean
}
class CreditCardProcessor {
+processPayment(amount: Decimal): Boolean
}
class Payment {
- paymentID: String
- amount: Decimal
- method: String
- timestamp: Date
+confirm(): Boolean
}
' Inheritance
Customer <|-- PremiumCustomer
' Interfaces
PaymentProcessor <|-- CreditCardProcessor
PaymentProcessor <|-- PayPalProcessor
' Associations
Customer "1" -- "0..*" Order : places
Order "1" -- "1..*" OrderItem : contains
OrderItem "1" -- "1" Product : references
Order "1" -- "1" Payment : has
PaymentProcessor "1" -- "1" Payment : processes
' Constraints
note right of Order
Status: [Pending, Confirmed, Shipped, Cancelled]
end note
note right of Product
Stock must be > 0 to be sold
end note
@enduml
ā Ā Use Case: Developer handoff, code generation, design documentation.
ReflectĀ real-world changesĀ in the system.
DocumentĀ refactoring,Ā deprecations,Ā new features.
SupportĀ technical debt trackingĀ andĀ system understanding.
May includeĀ deprecatedĀ classes/methods.
ShowĀ new classes,Ā renamed elements,Ā removed components.
UseĀ stereotypesĀ (<<deprecated>>,Ā <<singleton>>,Ā <<factory>>).
OftenĀ simplifiedĀ for readability.
@startuml
‘ Revamped Payment System: Strategy + Factory Pattern
@startuml
‘ Revamped Payment System: Strategy + Factory Pattern
‘ Interface
class PaymentProcessor {
+processPayment(amount: Decimal): Boolean
}
‘ Concrete Strategies
class CreditCardProcessor {
+processPayment(amount: Decimal): Boolean
}
class PayPalProcessor {
+processPayment(amount: Decimal): Boolean
}
class StripeProcessor {
+processPayment(amount: Decimal): Boolean
}
‘ Factory Pattern
class PaymentProcessorFactory {
+createProcessor(type: String): PaymentProcessor
+getAvailableTypes(): List<String>
}
‘ Service that uses the strategy
class OrderService {
– processor: PaymentProcessor
+createOrder(customer: Customer, items: List<OrderItem>): Order
+setPaymentProcessor(processor: PaymentProcessor): void
}
‘ Payment entity
class Payment {
– paymentID: String
– amount: Decimal
– method: String
– timestamp: Date
+confirm(): Boolean
}
‘ Customer and Order (simplified)
class Customer {
– customerID: String
– name: String
– email: String
+addOrder(order: Order): void
+getOrders(): List<Order>
}
class Order {
– orderID: String
– orderDate: Date
– status: OrderStatus
– total: Decimal
+calculateTotal(): Decimal
+validate(): Boolean
+save(): void
+cancel(): void
}
‘ Stereotypes for clarity
PaymentProcessor <<interface>>
CreditCardProcessor <<strategy>>
PayPalProcessor <<strategy>>
StripeProcessor <<strategy>>
PaymentProcessorFactory <<factory>>
OrderService <<service>>
‘ Inheritance: Strategy Pattern
CreditCardProcessor <|– PaymentProcessor
PayPalProcessor <|– PaymentProcessor
StripeProcessor <|– PaymentProcessor
‘ Factory creates processors
PaymentProcessorFactory “1” — “1” PaymentProcessor : creates
‘ OrderService uses a processor (dependency injection)
OrderService “1” — “1” PaymentProcessor : uses
‘ OrderService uses the factory to set processor
OrderService “1” — “1” PaymentProcessorFactory : configures via
‘ Payment depends on processor
Payment “1” — “1” PaymentProcessor : uses
‘ Associations
Customer “1” — “0..*” Order : places
Order “1” — “1..*” OrderItem : contains
OrderItem “1” — “1” Product : references
Order “1” — “1” Payment : has
‘ Constraints
note right of Order
Status: [Pending, Confirmed, Shipped, Cancelled]
end note
note right of Payment
Method: “CreditCard”, “PayPal”, “Stripe”
end note
note right of PaymentProcessorFactory
Supported types: “CreditCard”, “PayPal”, “Stripe”
Can be extended without modifying OrderService
end note
@enduml
ā Ā Use Case: Onboarding new developers, system refactoring, audit trails.
| Stage | Focus | Detail Level | Key Elements |
|---|---|---|---|
| 1. Requirements | Domain Concepts | High-Level | Entities, associations |
| 2. Analysis | System Structure | Medium | Attributes, operations, interfaces |
| 3. Implementation | Code Ready | High | Types, access modifiers, patterns |
| 4. Maintenance | System Evolution | Adaptive | Stereotypes, deprecations, simplification |
UseĀ @startumlĀ andĀ @endumlĀ to wrap diagrams.
UseĀ <<stereotype>>Ā for design patterns or metadata.
UseĀ note right ofĀ for documentation.
UseĀ +,Ā -,Ā #Ā for visibility (public,Ā private,Ā protected).
UseĀ <<interface>>,Ā <<abstract>>,Ā <<singleton>>Ā for clarity.
Generate images viaĀ PlantUML OnlineĀ or IDE plugins (VS Code, IntelliJ).
Class diagrams areĀ not staticĀ ā theyĀ evolve with the project. Use them strategically:
Early: Communicate with non-technical stakeholders.
Mid: Align developers on architecture.
Late: Guide implementation and code quality.
Post-Release: Maintain system knowledge.
ā Ā Pro Tip: Version-control your PlantUML files alongside code ā theyāre living documentation!
Class diagrams are more than diagrams ā they are maps of intent, blueprints of collaboration, and living records of architectural evolution. As weāve seen, their value isnāt in their initial form, but in how they adapt across the development lifecycle ā from the high-level abstractions of requirements to the precise, implementation-ready models of late-stage design.
The journey from isolated processor classes to a connected, strategy-driven system illustrates a fundamental truth: good design isnāt just about defining components ā itās about defining how they work together. When CreditCardProcessor, PayPalProcessor, and StripeProcessor are treated as interchangeable strategies ā orchestrated by a factory and injected into services ā the diagram ceases to be a static snapshot. It becomes a dynamic model of flexibility, scalability, and maintainability.
By using patterns like Strategy, Factory, and Dependency Injection, we transform isolated classes into a cohesive, extensible ecosystem. This isnāt just about better diagrams ā itās about building better software. It enables teams to:
Ultimately, the most powerful class diagrams are not those that show every field and method in detail ā but those that tell a story: a story of collaboration, adaptability, and forward-thinking design.
So as you sketch your next class diagram, ask yourself:
Are my classes just defined ā or are they connected?
Are they isolated ā or are they part of a system that can grow?
Because in the end, the best class diagrams donāt just describe what the system is ā they inspire how it should become.