Introduction
There is a moment in object-oriented design when the whole subject starts to feel different.
Until that point, it is easy to think of objects as tidy bundles of data with a few methods attached. Classes become containers, methods become utilities, and design becomes an exercise in deciding where to store information.
But responsibility-driven design asks us to look at the problem another way: not as a static arrangement of data, but as a system of cooperating agents.
Designing Object-Oriented Software
An object's potencies can often be modelled as behaviors proper to its class.
Rebecca Wirfs-Brock frames object-oriented design not just as a way of organizing code, but as a way of thinking about behavior, roles, and collaboration. Objects are not just things. Within the software system, they are the entities that make things happen. That leads to a striking shift in perspective.
In real life, the player rolls the dice. The dice do not roll themselves. But in software, it can make perfect sense to say that the Dice object rolls. Object-oriented design is not about copying the real world literally. It is about modelling capabilities and responsibilities coherently. If the behavior naturally belongs with the object, then the object should have the agency to carry it out.
The potency of an object is often best represented as behaviors on its class. The dice can't move itself. But if we modelled everything like that, then the customer or player would do everything in a system. The dice is in potency to be in the act of rolling , so we model that via a roll() method on the dice.
Object-Oriented Design
The book begins with a discussion of a methodical approach to object-oriented design ( OOD ).
The process of OOD is exploratory. The designer looks for classes, trying out a variety of schemes in order to discover the most natural and reasonable way to abstract the system:
- Find the classes in your system
- Determine what operations each class is responsible for performing
- Identify what knowledge it should maintain
- Determine the ways in which objects collaborate to discharge their responsibilities
These steps produce:
- a list of classes within your application
- a description of the knowledge and operations for which each class is responsible, and
- a description of collaborations between classes
With this information, you can begin the analytical stage of the design process:
- Look for inheritance relationships between classes
- Look at the behavior of each class
- Are there classes that share certain responsibilities?
- Can you factor out these shared sets of responsibilities into superclasses?
Next, analyze the collaborations between the classes in an effort to streamline them:
- Are there places in the system where the message traffic is heavy?
- Are there objects which collaborate with everybody?
- Are there objects which collaborate with nobody?
- Are there groups of classes that work close together?
The analysis of your preliminary design consists of factoring common responsibilities in order to build class hierarchies, and streamlining the collaborations between objects.
Abstraction
Abstraction is a natural mental process.
People typically understand the world by constructing mental models of portions of it. A mental model is a simplified view of how something works so that you can interact with it.
In essence, this process of model-building is the same as designing software, but software development is unique: software design produces a model that can be manipulated by a computer.
Software applications are complex because they model the complexity of the real world.
OOD encourages a view of the world as a system of cooperating and collaborating agents. It attempts to manage the complexity inherent in real-world problems by abstracting knowledge and encapsulating it within objects. Finding or creating these objects is a problem of structuring knowledge and activities.
Objects that share the same behavior are said to belong to the same class.
A class allows you to build a taxonomy of objects on an abstract, conceptual level.
Abstraction is the key to designing good software.
Objects as agents
One of the strongest ideas in responsibility-driven design ( RDD ) is that objects should be understood as agents within a system, each having a meaningful role to play. An object is defined not only by what it is, but by what it knows and what it does.
In more procedural styles of thinking, the system often ends up organized around external control: one part of the code reaches into another, pulls out data, makes decisions, and pushes results back in. The objects themselves remain passive. In RDD , by contrast, behavior belongs with the object that is responsible for it. The object becomes an active participant in the model.
For example, saying that a Dice object can roll() is an expression of agency in the design. The object encapsulates the behavior that belongs to it in the system. In that sense, the design is not less realistic. It is more coherent. We are modelling the real world sensibly, not duplicating it verbatim.
Responsibility means knowing and doing
Wirfs-Brock's treatment of responsibility is grounded in two simple questions:
- What does this object know?
- What can this object do?
An object's responsibilities include both the knowledge it maintains—its state, invariants, and relationships—and the actions it can perform.
Some objects are responsible for knowing things: their state, related objects, or information derived from their own data. Other objects are responsible for doing things: carrying out an action, coordinating work, or fulfilling a request. Most important objects do some of both.
This is a better starting point than thinking immediately in terms of data structures and helper functions. It focuses attention on purpose . Before worrying about implementation, we ask what role an object plays in the system and what responsibilities naturally follow from that role.
That way of thinking tends to produce designs in which behavior is not scattered arbitrarily across utility classes or procedural controllers. Instead, intelligence is distributed across cooperating objects. Each object carries a smaller, more coherent piece of the system.
And that, in turn, usually produces designs that are easier to understand and easier to change.
It also aligns with sound thinking. Actions should be ordered to an end. The final cause, therefore, is last in execution but first in intention. A focus on purpose should precede a focus on details.
This reflects the faculties of our intellect, which are to know and to will.
Roles, collaborations, and contracts
RDD becomes even more interesting when we move from single objects to their interactions.
How do classes fulfill their responsibilities? They can do so in two ways: by performing the necessary computation themselves, or by collaborating with other classes.
Objects do not exist in isolation. They collaborate. One object requests a service; another fulfills it. In Wirfs-Brock's terminology, these are client and server roles .
An object is not permanently a client or permanently a server. It may be one in one interaction and the other somewhere else. What matters is the relationship in a given collaboration.
This keeps the focus on behavior rather than status. A collaboration is not just a call between classes. It is a purposeful interaction in which one object depends on another to help fulfill a responsibility.
That leads naturally to the idea of a contract. A contract is the set of requests a client can make of a server. In modern terms, we might think of that as an interface or public protocol. But the responsibility-driven framing is slightly richer. The contract is not just a technical surface. It expresses how one object can rely on another within the design. A contract represents a cohesive set of responsibilities.
The pattern of collaborations reveals the flow of control and information during system execution.
In order to determine collaborations between classes, begin by analyzing the interactions of each class. Examine responsibilities for dependencies. To identify collaborations, ask the following questions for each responsibility of each class:
- Is the class capable of fulfilling this responsibility itself?
- If not, what does it need?
- From what other class can it acquire what it needs?
- What does this class do or know?
- What other classes need the result or information?
If a class has no meaningful interactions with other classes, it may not belong in the design.
This view of software is far more dynamic than a class diagram alone. It invites us to think in terms of who is asking, who is answering, and why that collaboration exists. A system becomes not just a taxonomy of classes, but a network of roles and agreements.
Finding classes by finding responsibilities
Another appealing feature of this approach is that it gives a practical way into design.
Responsibilities can be identified in a number of ways. At the start, two of the most fruitful sources are the requirements specification and the classes you have already identified.
Wirfs-Brock suggests starting with the requirements and looking for noun phrases as candidate classes. Once you have candidate classes, you do not stop at naming them. You ask what purpose they serve, what responsibilities they carry, and how they collaborate with others.
In practical terms, emphasize the important and eliminate the irrelevant:
- Model physical objects, such as disks or printers
- Model conceptual entities that form a cohesive abstraction such as a window or a file
- Eliminate competing synonyms — select the best, remove the rest
- Be wary of adjectives, focus instead on nouns — e.g. Point rather than Start Point and Stop Point
- Be wary of sentences in the passive voice, or those whose subjects are not part of the system
- Model categories of classes
- Model known interfaces to the outside world
- Model the values of attributes of objects, but not the attributes themselves
Highlight all the verbs, and use your judgment to determine which of these clearly present actions that some object within the system must perform. Everywhere information is mentioned, note it. Information that some object within the system must maintain and manipulate also represents responsibilities.
A useful way to find responsibilities is to perform a walk-through of the system as a whole. Imagine how the system will be invoked, and go through a variety of scenarios using as many system capabilities as possible. Look for places where something must occur as a result of input to the system.
Look at the classes you have identified. The name you chose for a class suggests a responsibility, and possibly others. These names represent roles within your system; they imply responsibilities for the objects that must fulfill those roles. The statement of purpose for each class may also contain additional responsibilities. Comparing and contrasting the roles of classes can also generate new responsibilities. Examine the relationships between them to gain insight in assigning their responsibilities:
- the is-kind-of relationship
- the is-analogous-to relationship, and
- the is-part-of relationship.
When a class seems to be a kind of another class, it is frequently a sign of a subclass-superclass relationship between them. Such a relationship is often revealed by shared attributes.
If class X bears a relationship to another part of the system that is analogous to that borne by class Y , then perhaps class X and Y share the same (or analogous) responsibilities. Perhaps they are both kinds of something else, an as-yet-unidentified superclass.
A clear distinction between whole and part may help us determine where responsibilities for certain behavior ought to be, mainly by narrowing the scope of possibilities. An object composed of other objects must always know about its parts.
If a class has no meaningful responsibilities, no useful collaborations, or no clear statement of purpose, it may not belong. Naming a thing is not enough. The abstraction has to earn its place in the design.
RDD pushes back against the temptation to equate object-oriented analysis with noun collection. Nouns are useful clues, but they are not the design.
The design emerges when responsibilities are assigned and collaborations take shape.
Assigning Responsibilities
Assign each responsibility you have identified to the class or classes it should logically belong to.
- Evenly distribute system intelligence
- State responsibilities as generally as possible
- Keep behavior with related information, if any
- Keep information about one thing in one place
- Share responsibilities among related objects
Objects are not data holders with methods attached
A weak object model often treats objects as passive records whose data is manipulated elsewhere. The object stores values; another part of the system decides what they mean and what should happen next. In that kind of design, methods often become thin wrappers, and the real intelligence drifts toward procedural services or manager classes.
RDD asks us to put behavior where it belongs. An object should not simply expose its internals and wait to be told what to do. It should carry the responsibilities appropriate to its role.
That is what the idea of objects as agents really gets at. Agency in software design means that an object has a meaningful part to play in the behavior of the system. It knows certain things, does certain things, and collaborates with other objects to fulfill the broader design.
Once you start seeing objects this way, some designs begin to look obviously wrong. A giant all-knowing controller, a passive anemic domain model, or a network of data structures manipulated by external services all begin to feel less object-oriented, even if they use classes and methods.
Object design reflects the world of living things rather than the mechanical parts of an engine.
Object stereotypes as design aids
Another useful strand in Wirfs-Brock's work is the idea of object stereotypes.
Not every object plays the same kind of role. Some objects primarily represent things in the domain. Some coordinate activity. Some interface with the outside world. Some exist to organize communication between other parts of the system.
A domain object should not behave like a coordinator. A control object should not become a dumping ground for business logic. An interface object should not start accumulating domain knowledge. Once we see those distinctions more clearly, it becomes easier to assign responsibilities well.
That also connects RDD with a wider design discipline: good systems are shaped not only by choosing classes, but by recognizing the kinds of work different objects are best suited to carry.
Cohesion and the distribution of intelligence
One of the recurring lessons in this style of design is that system intelligence should be distributed.
If one class seems to know everything, coordinate everything, and decide everything, that is usually a sign that responsibilities have not been assigned well. A design becomes more flexible when knowledge and behavior are spread across several cooperating objects, each with a focused purpose.
This is closely related to cohesion. A class should support a coherent set of responsibilities. A contract should express a coherent set of services. When those responsibilities start to pull in unrelated directions, the design becomes harder to refine and harder to understand.
That does not mean fragmenting the system into tiny pieces for the sake of it. Good design still balances comprehensibility with modularity. But RDD gives a clear heuristic: if an object or class card has become overloaded, stop and reconsider how the intelligence of the system has been distributed.
In practice, this is one of the best safeguards against bloated classes and tangled collaborations.
Why this still matters
RDD teaches us that object-oriented design is about organizing a system around a set of cooperating agents, each with meaningful responsibilities, clear roles, and intelligible collaborations.
Instead of passive structures manipulated from the outside, intelligent objects participate in the work of the system. Personally, that remains one of the most attractive ideas in object-oriented design.
Rebecca Wirfs-Brock's work captures it beautifully. It does not just explain how to divide software into classes. It teaches a better way to think about what objects are for in the first place.
Object Role Stereotypes
Examples of modern object role stereotypes| Stereotype | Role in the system | Typical responsibility |
|---|---|---|
| Entity / Domain Object | Represents a core concept in the domain | Maintains domain state and behavior relevant to that concept |
| Value Object | Represents a descriptive value with no distinct identity | Encapsulates attributes and related validation or comparison logic |
| Service Object | Performs work that does not naturally belong to a single domain object | Carries out an action, calculation, or operation for other objects |
| Controller / Coordinator | Orchestrates a use case or flow of work | Delegates tasks and manages collaboration between objects |
| Repository | Mediates access to stored domain objects | Retrieves and persists objects without mixing storage concerns into the domain |
| Factory | Creates objects that require non-trivial construction | Encapsulates creation logic and hides setup complexity |
| Adapter / Gateway | Connects the system to an external service or API | Translates between the internal model and an outside interface |
| Router | Directs incoming requests to the appropriate handler | Maps routes, paths, or messages to the correct object or flow |
| Presenter / View Model | Shapes data for display | Prepares domain information for a user interface without owning business rules |
| Policy / Strategy Object | Encapsulates a variable rule or algorithm | Applies interchangeable logic such as pricing, tax, or validation rules |
| Validator | Checks whether data or actions satisfy required constraints | Enforces rules before deeper processing occurs |
| Mapper | Translates between representations | Converts between domain objects, DTOs, persistence records, or API payloads |
| Message Handler | Handles a specific command, event, or message | Reacts to an incoming request and triggers the appropriate behavior |
| Assembler | Composes richer objects from simpler parts | Builds a useful structure or aggregate from lower-level pieces |
| Scheduler | Coordinates timed, deferred, or recurring work | Determines when something should happen rather than performing the work itself |
| Specification | Represents a business rule as a reusable predicate | Expresses whether some object or action satisfies a defined condition |