Design Patterns and Java Architecture
Apply SOLID, common design patterns, dependency inversion, layered architecture, hexagonal architecture, and domain-oriented design.
SOLID Principles
SOLID is a set of design heuristics. Single Responsibility encourages cohesive responsibilities. Open/Closed encourages extension without repeatedly modifying stable behavior. Liskov Substitution focuses on substitutability. Interface Segregation avoids irrelevant dependencies. Dependency Inversion encourages high-level policy to depend on abstractions.
interface NotificationSender {
void send(String recipient, String message);
}
final class OrderService {
private final NotificationSender sender;
OrderService(NotificationSender sender) {
this.sender = sender;
}
}
The value is reduced coupling and easier testing, not creating an interface for every class.
- SOLID is about managing change and coupling.
- Dependency inversion improves testability and architectural boundaries.
- Do not introduce abstractions without a meaningful reason.
Common Patterns in Java
Common patterns include Factory, Builder, Strategy, Adapter, Decorator, Observer, Template Method, Command, and State.
interface PricingStrategy {
long price(Order order);
}
final class RegularPricing implements PricingStrategy {
public long price(Order order) {
return order.subtotal();
}
}
Strategy is polymorphism used to select interchangeable behavior. Builder helps construct complex immutable objects. Adapter translates one interface into another. Decorator adds behavior without modifying the wrapped implementation.
- Patterns are reusable design ideas, not mandatory class structures.
- Strategy uses polymorphism to vary behavior.
- Prefer the simplest design that remains easy to change.
Layered and Hexagonal Architecture
Layered architecture commonly separates controllers, services, and repositories. Hexagonal architecture separates application/domain logic from external adapters through ports.
HTTP / Messaging / CLI
|
Adapters
|
Ports
|
Application / Domain
|
Ports
|
DB / APIs / Files
The key idea is dependency direction: business rules should not be tightly coupled to HTTP frameworks, database drivers, or cloud SDKs.
- Architecture is primarily about dependency direction.
- Ports isolate business logic from infrastructure.
- Adapters are translation boundaries, not business-rule containers.
Domain Modeling and Maintainability
A good domain model captures meaningful business invariants. Value objects are useful when a concept has identity-independent rules such as money, email addresses, coordinates, or date ranges.
public final class Money {
private final BigDecimal amount;
private final Currency currency;
public Money(BigDecimal amount, Currency currency) {
this.amount = amount;
this.currency = currency;
}
}
Avoid both extremes: scattering business rules everywhere and turning every simple CRUD operation into an elaborate framework.
- Put important invariants near the model that owns them.
- Value objects capture concepts and their rules.
- Architecture should reflect real business complexity rather than ceremony.