Introduction
Good software design comes from making consistent, thoughtful decisions about where responsibilities belong, how objects collaborate, and how systems respond to change over time.
That is where GRASP patterns become especially valuable.
Effective software development also depends on understanding requirements, identifying domain rules, modeling real-world concepts, and planning iterations in a way that reduces risk while delivering value to clients. Together, these ideas form a strong foundation for building systems that are both useful and resilient.
In this post, I bring together my notes from studying Craig Larman's Applying UML and Patterns , looking at the core GRASP patterns and how they relate to responsibility-driven design.
GRASP stands for General Responsibility Assignment Software Patterns. There are nine of them in total.
Information Expert
The Information Expert pattern is one of the most fundamental ideas in object-oriented design.
Its core principle is simple: assign a responsibility to the class that has the information needed to fulfill it .
When responsibilities are placed where the relevant knowledge exists, code becomes easier to maintain, easier to understand, and less dependent on other parts of the system. Instead of scattering logic across unrelated classes, Information Expert encourages us to keep behavior close to the data it depends on.
For example, if a Sale object knows about its line items, then it makes sense for Sale to be responsible for calculating the total. Giving that responsibility to some unrelated controller or utility class would increase unnecessary coupling and make the design less cohesive.
A simple real-world analogy is the workplace: you would go to the payroll officer to check pay details, and to the HR manager for clarification on company policies. In the same way, when assigning responsibility in software design, the question to ask is: who owns this information naturally?
Assigning responsibilities to the right information holder often improves cohesion and reduces the need for one class to know too much about another. Information Expert is an excellent default: if a class has the necessary information, it is usually the best candidate to handle the responsibility.
Creator
The Creator pattern addresses a practical design question: who should be responsible for creating an object? While object creation can sometimes be handled by factories or dedicated construction mechanisms, GRASP offers a useful default guideline for assigning this responsibility.
A class is a good candidate to create another class if it contains it, aggregates it, records it, closely uses it, or has the data needed to initialize it. In other words, object creation should usually be assigned to the class that already has a strong relationship with the thing being created.
For example, if a Sale contains multiple SalesLineItem objects, then Sale is a natural candidate for creating those line items. Since it already manages them and holds the context in which they exist, it makes sense for it to control when and how they are created.
The Creator pattern is useful because it tends to support high cohesion and low coupling. The class doing the creation is usually already closely connected to the created object, so the design remains more organized and easier to follow. Rather than having some distant class instantiate objects it barely understands, Creator encourages responsibility to stay near the relevant part of the model.
In more complex systems, object creation may be delegated to factories, builders, or dependency injection frameworks. Even so, Creator is a sensible default.
Controller
The Controller pattern asks: what object should receive and coordinate a system operation?
The key idea is that the UI itself should not handle the core application logic. Instead, that responsibility should be assigned to a separate object that represents either the overall system, a major subsystem, or a specific use case. In GRASP , this object is called a controller.
If too much logic is placed directly in the UI layer, the design becomes harder to test, harder to maintain, and more tightly coupled to the interface. By introducing a controller, we separate concerns more cleanly: the interface captures the request, and the controller coordinates the work needed to fulfill it.
For example, in a point-of-sale system, when a cashier presses a button to complete a sale, the screen itself should not be calculating totals, validating rules, and updating records. Instead, that request could be passed to a Register or SaleController object, which then coordinates the relevant domain objects.
GRASP describes two common kinds of controller. One is a façade controller , which represents the overall system or a major subsystem. The other is a use-case controller , which represents a particular scenario or session within the system. Both approaches aim to keep system operations out of the UI layer and place them in an object better suited to managing them.
Low Coupling
The Low Coupling pattern focuses on one of the central goals of good software design: reducing unnecessary dependency between classes .
In simple terms, coupling refers to how strongly one part of a system depends on another. When classes are highly coupled, changes in one area can easily ripple outward and affect many others. That makes the system harder to understand, harder to test, and more expensive to maintain. Low Coupling encourages us to assign responsibilities in a way that keeps these dependencies to a minimum.
The goal is to avoid unnecessary connections, especially ones that make the design rigid or fragile. A design with low coupling is easier to reason about because each class has fewer external entanglements.
For example, if a reporting class directly depends on the internal details of a database class, a formatting class, and several domain objects all at once, it becomes more vulnerable to change. A small change in any of those parts might force the reporting class to change as well. A better design would limit how much that class needs to know, perhaps by interacting through clearer interfaces or by delegating some responsibilities elsewhere.
Low Coupling is especially useful as an evaluative principle. When comparing two design options, one of the best questions to ask is: which design creates fewer dependencies between unrelated parts of the system? The option with lower coupling will often be easier to extend and less risky to modify later.
The Low Coupling pattern helps make software more resilient to change.
High Cohesion
The High Cohesion pattern is concerned with keeping classes focused, clear, and manageable.
Cohesion refers to how closely related the responsibilities of a class are. A highly cohesive class has a small, well-related set of responsibilities that fit naturally together. A class with low cohesion, by contrast, tends to take on too many unrelated tasks, making it harder to understand, reuse, and maintain.
This matters because overloaded classes often become confusing quickly. When a class is responsible for handling user input, performing calculations, managing persistence, and coordinating other objects, its design becomes messy and difficult to reason about. High Cohesion encourages a more disciplined approach: each class should do a coherent set of things, and those things should belong together.
For example, imagine a Register class in a sales system. If it starts processing payments, formatting receipts, storing database records, and validating inventory all by itself, it soon becomes bloated. A more cohesive design would distribute those responsibilities to other objects better suited to them, leaving Register with a clearer and more focused role.
High Cohesion is closely related to Low Coupling . When classes are more focused, they usually need fewer unnecessary relationships with other classes. Likewise, when responsibilities are assigned carefully, each class becomes easier to understand in isolation. The two principles often reinforce each other.
Like Low Coupling , High Cohesion is best used as a way to evaluate design choices.
When deciding where a responsibility should go, it is worth asking whether assigning it to a given class will keep that class focused or make it more scattered. In that sense, High Cohesion is less about following a strict rule and more about preserving clarity in the design over time.
Polymorphism
The Polymorphism pattern addresses a common design problem: who should be responsible when behavior varies depending on type?
The GRASP answer is that the responsibility should be assigned to the types for which the behavior varies, using polymorphic operations. When behavior is pushed into the relevant classes, the design becomes easier to extend and easier to read. Instead of centralizing variation in a single block of if or switch logic, we distribute it across the objects that actually differ.
For example, imagine a payment system with different payment types such as CashPayment, CardPayment, and VoucherPayment. If processing logic is written in one place using conditionals for each type, that code can quickly become awkward and harder to maintain. A more object-oriented approach would define a common interface or superclass for Payment, and let each subtype implement its own processPayment() behavior.
This has an important benefit: adding a new variation becomes much easier. Instead of modifying a large decision structure in an existing class, we can often add a new subclass with its own implementation. That reduces the risk of breaking existing behavior and helps the design remain open to extension.
Polymorphism also supports other GRASP goals such as Low Coupling , High Cohesion , and Protected Variations . Each class takes responsibility for its own behavior, and client code can interact with objects through a shared abstraction rather than depending on concrete details. Polymorphism is a design principle for handling variation in a clean and scalable way.
Pure Fabrication
The Pure Fabrication pattern is used when assigning a responsibility to a domain object would lead to poor design. GRASP suggests creating an artificial class specifically to handle that responsibility.
The name sounds slightly odd at first, but the idea is practical. Sometimes the most natural real-world object is not actually the best place to put a piece of behavior. If assigning that responsibility would reduce cohesion or increase coupling, it can be better to invent a new class that does not represent a domain concept at all, but exists purely to support a cleaner design.
For example, consider a Sale object that needs to be persisted. We could make Sale responsible for saving itself, but that would mix domain logic with persistence concerns. Instead, we might introduce a SaleRepository or SalePersistenceService class to handle storage. That class is not part of the business domain in the same way that Sale is, but it helps preserve a better separation of responsibilities.
This is the essence of Pure Fabrication : create a highly cohesive helper class when doing so improves the overall structure of the system . The fabricated class is a deliberate design tool used to protect more important qualities such as High Cohesion , Low Coupling , and reuse.
In practice, many common software design elements are examples of Pure Fabrication .
Repositories, services, mappers, and adapters often exist not because they directly model something in the business domain, but because they provide a cleaner way to organize responsibilities.
Pure Fabrication reminds us that object-oriented design is not only about mirroring the real world. It is also about building software structures that are understandable, flexible, and maintainable.
Sometimes that means introducing a class whose main purpose is to make the design itself work better.
Indirection
The Indirection pattern is about reducing direct coupling between parts of a system by introducing an intermediate object to handle communication between them.
In many designs, two components can become too tightly connected if they interact with each other directly. When that happens, changes in one part of the system are more likely to affect the other.
Indirection offers a way to soften that dependency by placing another object or interface in between, one that mediates the interaction.
This principle is what makes patterns like Strategy so useful. Rather than embedding a variable algorithm directly inside a class, we can delegate that behavior to a separate object that implements a common interface. In that sense, Strategy is a practical example of indirection: the client does not need to know the exact details of how the behavior is carried out, only which abstraction it should rely on.
A classic example is tax calculation. A Sale object may need to determine the correct tax, but tax rules can vary depending on jurisdiction, product category, or business policy. Instead of hard-coding all of that logic inside Sale, the design can delegate the calculation to a TaxCalculator object. Different implementations of that calculator can then be introduced for different contexts. This keeps the sale logic cleaner and makes it easier to update tax behavior without rewriting the core domain object.
The value of Indirection is that it helps preserve Low Coupling .
Rather than allowing every class to depend directly on every service or subsystem it needs, we introduce carefully chosen intermediaries that manage those relationships. This can make the system easier to change, test, and understand.
Indirection should be used with care. Adding too many layers can make a design feel overly complex or difficult to follow. The point is not to insert intermediaries everywhere, but to use them when they meaningfully reduce dependency or isolate unstable parts of the system.
In practice, many common design structures make use of Indirection. Controllers, service layers, adapters, and brokers all act as intermediaries in one form or another.
The pattern is less about a specific class type and more about a useful design strategy: when direct communication creates problems, introduce a mediator that helps manage the relationship more cleanly .
Protected Variations
The Protected Variations pattern is concerned with one of the most persistent challenges in software design: change. Systems rarely stay still for long, and some parts are far more likely to vary than others. The aim of this pattern is to identify those unstable points early and design the system so that their changes do not have an undesirable impact elsewhere.
The core idea is: where variation is expected, create a stable interface around it. That way, the rest of the system can depend on something consistent, even if the underlying implementation changes over time.
This builds naturally on Polymorphism and Indirection . If a part of the system is likely to change, whether that is an external service, a business rule, or a family of algorithms, separate that variation behind an abstraction. The code then depends on the stable contract rather than the changing details.
A good example is again the tax calculator. Tax rules may change because of legislation, geography, product type, or company policy. If those rules are embedded directly in the Sale class, every change to tax logic risks affecting the heart of the sales system. A better design is to place tax calculation behind a TaxCalculator interface, with different implementations handling the varying rules. In that case, the interface acts as the protected point, shielding the rest of the design from the instability of tax policy.
The same thinking applies more broadly. Payment gateways, file formats, third-party APIs, reporting rules, and persistence mechanisms are all common sources of variation. Protected Variations encourages us to ask: what is likely to change here, and how can the rest of the design be insulated from it?
This pattern shifts design thinking from the immediate problem to the long-term shape of the system. Rather than reacting to change after it causes damage, we anticipate likely instability and place boundaries around it. That does not mean trying to predict every possible future requirement, but it does mean paying attention to obvious points of volatility and designing with them in mind.
In that sense, Protected Variations is one of the most strategic GRASP patterns. It helps produce software that is more robust, more adaptable, and less fragile when the real world inevitably changes around it.
General Responsibility Assignment Software Patterns
A summary of the nine GRASP patterns.| Name | Pattern | Description |
|---|---|---|
| Information Expert | A general principle of object design and responsibility assignment. | Assign a responsibility to the class that has the information needed to fulfill it. |
| Creator | Determines who should create an object. | Assign class B the responsibility to create an instance of class A if B contains, aggregates, records, closely uses, or has the initializing data for A. |
| Controller | Identifies what object should receive and coordinate a system operation. | Assign responsibility to an object that represents the overall system, a major subsystem, or a specific use case scenario. |
| Low Coupling | Reduces the impact of change. | Assign responsibilities so that unnecessary coupling remains low. Use this principle to evaluate design alternatives. |
| High Cohesion | Keeps objects focused, understandable, and manageable. | Assign responsibilities so that cohesion remains high. Use this principle to evaluate design alternatives. |
| Polymorphism | Handles behavior that varies by type. | When related behaviors vary by class, assign responsibility for the behavior to the types for which the behavior varies, using polymorphic operations. |
| Pure Fabrication | Preserves cohesion and low coupling when no natural domain class is suitable. | Assign a highly cohesive set of responsibilities to an artificial class that does not represent a domain concept, in order to support reuse, low coupling, and high cohesion. |
| Indirection | Avoids direct coupling between components. | Assign responsibility to an intermediate object that mediates between other components or services so that they are not directly coupled. |
| Protected Variations | Shields other elements from instability or change. | Identify points of predicted variation or instability, and assign responsibilities to create a stable interface around them. |
Conclusion
GRASP patterns provide a practical way to think about responsibility assignment in object-oriented design.
Rather than offering rigid formulas, they encourage better design judgment: place behavior where the relevant knowledge exists, keep classes focused, reduce unnecessary dependencies, and protect the system from variation where possible.
What makes GRASP especially useful is that these patterns are not isolated rules. They support one another. Information Expert often improves cohesion; indirection can help protect variations; polymorphism reduces brittle conditional logic. Together, they offer a vocabulary for reasoning about why one design feels cleaner, more flexible, and easier to maintain than another.
For anyone learning object-oriented design, GRASP is valuable not only because it helps produce better software, but because it teaches a better way to think about software structure in the first place.
Craig Larman's book is a masterclass on the subject. Highly recommended.