Responsibility-Driven Design in Practice
When I first started reading Responsibility-Driven Design, I thought it was just another name for object-oriented programming. That changed after my team shipped a feature where the wrong object was responsible for calculating shipping costs, and six weeks later we were refactoring because the business logic had spread across five different classes with no clear owner. The book's central idea is straightforward: design your classes around what they should know and do, not around the data structures they hold or the operations that need to happen. Every class has responsibilities. You figure out which responsibilities belong where, and then you model the system accordingly. It sounds simple because it is, but applying it consistently is where most people struggle.
Designing Object Oriented Software Rebecca Wirfs Brock
Rebecca Wirfs-Brock's approach centers on identifying information responsibilities for each class. An information responsibility is essentially anything a class needs to know to do its job. This could be maintaining a list of customers, tracking the current state of an order, or computing a tax calculation based on a region. There are two types of responsibilities you need to distinguish. Design responsibilities cover the internal structure of a class: what it knows, how it initializes itself, what data it keeps private, and how it exposes its interface. Mission responsibilities are the observable behaviors: what the class does when other objects send it messages. In practice, you start by listing the nouns you encounter during requirements gathering, then assigning each noun a potential class. From there you ask: what does this thing need to know? What should it be able to do? The answer to those questions becomes the class's responsibilities. Everything else is detail.
One thing the book does not emphasize enough is that responsibility assignment is iterative. My first attempt at a banking application had a class called AccountTransaction that was responsible for validating the transaction, updating the balance, logging the event, and sending a notification email. It became clear after about two days that nothing in that class actually wanted to be coupled to an email service. I split it into four classes and the design improved immediately.
Get the Full Details
Identifying Responsibilities Without Overcomplicating Things
The technique most people find useful is the CRC card method, even if you never physically write cards. You take a class name, list its collaborators, and write down its responsibilities. The point is not the physical artifact. The point is forcing yourself to articulate what each class owns before you start coding. Here is a practical example. Consider a task management system. The NounList gives you Task, Project, User, and Deadline. For the Task class, you would write something like this: Task responsibilities:
Know its title and description. Know its status (open, in progress, closed). Know which Project it belongs to. Know its assigned User. Notify its Project when its status changes. Report whether it is overdue based on its Deadline. That last point about reporting overdue status is where beginners tend to make mistakes. They put the overdue calculation inside the Deadline class, because a Deadline knows dates. But Deadline should not know about Task. The Task knows its own Deadline and can determine whether it is overdue. This is the kind of decision that comes from sitting down and arguing about responsibilities with your team. I ran into a specific edge case once where a NotificationService was supposed to send reminders for overdue tasks. The initial design had the Task class calling the NotificationService directly. This created a circular dependency problem when the NotificationService needed to query the Task repository to find all overdue tasks. The workaround was introducing a mediator: the Task class only published an event when it became overdue, and the NotificationService subscribed to those events. No direct coupling, no circular dependency. The book does not cover event sourcing extensively, but this pattern emerges naturally from clean responsibility assignment.
Common Pitfalls That Wreck Designs
The most common mistake I see is treating every noun as a class. If your requirements mention "order," "line item," "customer," "payment," and "shipping address," that does not mean you need five classes. Sometimes "shipping address" is just an attribute of the Order class. Sometimes "line item" is a value object, not a full entity with identity. Another pitfall is giving classes too many mission responsibilities. A class that handles both authentication and authorization is asking for trouble. Authentication answers the question: who are you? Authorization answers: what are you allowed to do? These are fundamentally different responsibilities that evolve at different rates. When security requirements change, you do not want to touch authorization logic, and vice versa. There is also a subtle issue with information responsibilities. Classes should not know more than they need to. I have seen data access objects that return entire rows from a database table, including fields the consumer never uses. This couples the consumer to the storage schema and makes refactoring painful. Return only what is needed for the responsibility at hand.

The book recommends a process called "Design Partners" where two developers work together to assign responsibilities. One acts as the designer and articulates the reasoning, while the other acts as the critic and challenges every decision. This process caught problems in my designs far more often than solo work ever did. I have found it reduces the time spent on later refactoring by roughly half, though that depends on how tangled the initial design was.
When Responsibility-Driven Design Does Not Work Well
There are scenarios where this approach struggles. If you are working with a heavily normalized database and your main concern is data integrity, a domain-driven design overlay might feel redundant. Some teams prefer to let the database schema drive the object model, especially in legacy systems where the data layer is the source of truth. Another limitation is real-time systems. Responsibility assignment works well for business applications with clear bounded contexts. It is less helpful when you are designing hardware controllers or embedded systems where timing constraints dominate over semantic responsibilities. In those cases, you might find state machine diagrams or component-based architectures more productive. I worked on a project once where we applied responsibility-driven design to a high-frequency trading engine. The designers argued for days about whether the Order class or the ExecutionEngine class should be responsible for managing order lifecycle events. The problem was that in a system with microsecond latency requirements, the abstraction itself introduced overhead that mattered. We ended up abandoning the pure RDE approach and used a hybrid model with explicit performance budgets for each component.
Practical Steps to Start Using This Today
Begin by writing down the domain vocabulary from your requirements. Do not skip this. It sounds trivial but it forces you to confront the actual language of the problem space rather than importing abstractions from other projects. Next, group related nouns into candidate classes. Ask what each one should know and do. Use CRC cards or a simple spreadsheet. The format does not matter. What matters is that you externalize your thinking before writing code. Then look for responsibilities that cross class boundaries. If a Task needs to know about Project internals to update its status, that is a smell. Either the Project should expose a method for status updates, or the responsibility assignment is wrong. Refactor until the responsibility sits in the right place.

Finally, validate your design by walking through typical use cases. Imagine a user creating a task, assigning it, and marking it complete. Follow the object interactions and check whether each class is handling its own responsibilities cleanly. If you find yourself passing the same data between three objects before it gets used, you likely have a design smell. This process usually takes a team one or two sessions for a medium-sized module. The alternative is writing the code first and spending three times as long untangling it later.