OOAD starts with responsibility, not just classes.
Object-oriented analysis and design is about shaping software around meaningful responsibilities, stable boundaries, and collaborations that can evolve without becoming brittle.
What OOAD is really for
OOAD gives us a way to move from problem understanding into software structure. Analysis asks what concepts, actors, and workflows exist in the domain. Design asks how those concepts should collaborate in code.
Good OOAD is less about drawing many diagrams and more about choosing boundaries that make change safer. A well-designed model reduces duplication, keeps responsibilities focused, and lets behavior live close to the data it depends on.
- Model the domain in terms the business or technical problem already uses.
- Assign responsibilities before worrying about frameworks or infrastructure.
- Prefer explicit collaborations over large, god-like classes.
- Keep invariants and business rules near the objects that own them.
Core OOAD concepts
Encapsulation keeps state and behavior together. Abstraction helps us focus on what matters. Polymorphism helps us extend behavior without littering code with brittle conditionals. Composition lets us assemble behavior while keeping parts replaceable.
Coupling and cohesion are the daily health metrics of OOAD. High cohesion means a module has a clear purpose. Low coupling means a module does not need to know too much about the world around it.
- Encapsulation: protect invariants and hide unnecessary detail.
- Abstraction: expose intent, not incidental complexity.
- Polymorphism: swap behavior behind stable contracts.
- Composition over inheritance: assemble behavior without deep hierarchies.
- Cohesion: keep related rules together.
- Coupling: depend on as little as possible.
Modeling views that actually help
Use cases explain what users or systems need. Class models show key domain concepts and relationships. Sequence-style thinking reveals message flow and responsibility boundaries. State thinking is useful when behavior depends on lifecycle.
You do not need heavyweight artifacts for every view. Lightweight sketches or narrative diagrams are often enough if they clarify responsibility and reduce design ambiguity.
- Use case view: who needs what outcome?
- Structural view: what are the stable concepts and relationships?
- Interaction view: which objects collaborate to fulfill a scenario?
- Lifecycle view: how does behavior change over time?
Example: modeling order approval with explicit responsibilities
This example keeps validation, policy, and orchestration separated. The service coordinates. The policy decides. The entity holds state and guards transitions.
Java example
public final class Order {
private final String id;
private final BigDecimal total;
private boolean approved;
public Order(String id, BigDecimal total) {
this.id = id;
this.total = total;
}
public BigDecimal total() {
return total;
}
public void approve() {
if (approved) {
throw new IllegalStateException("Order is already approved");
}
approved = true;
}
}
public interface ApprovalPolicy {
boolean canApprove(Order order);
}
public final class LimitBasedApprovalPolicy implements ApprovalPolicy {
private final BigDecimal threshold;
public LimitBasedApprovalPolicy(BigDecimal threshold) {
this.threshold = threshold;
}
@Override
public boolean canApprove(Order order) {
return order.total().compareTo(threshold) <= 0;
}
}
public final class OrderApprovalService {
private final ApprovalPolicy approvalPolicy;
public OrderApprovalService(ApprovalPolicy approvalPolicy) {
this.approvalPolicy = approvalPolicy;
}
public void approve(Order order) {
if (!approvalPolicy.canApprove(order)) {
throw new IllegalStateException("Approval threshold exceeded");
}
order.approve();
}
}
Design heuristics worth keeping
Ask which object has the information to do the job. Ask whether a behavior is likely to change independently. Ask whether a collaboration is stable enough to become an interface. These small questions prevent large structural problems later.
- Tell, don't ask: let objects do work instead of leaking data everywhere.
- Keep domain logic out of controllers and infrastructure glue.
- Create interfaces where variability is real, not speculative.
- Make illegal states hard to represent.
- Refactor toward clearer names and sharper boundaries as understanding improves.