GRASP: как правильно распределять ответственность между объектами
Sorry for my English! English is not my native language. One of the reasons to create this blog is to improve my English writing. So I will be highly obliged if you will help me with this. If you find a grammar error on this page, please select it with your mouse and press Ctrl+Enter.
I want to tell you about GRASP principles.
If you are already familiar with OOP, and especially with SOLID, some of the ideas behind GRASP may look familiar. You may even realize that you have been using some of them in your work for years without knowing their specific names.
That was my experience with GRASP.
Some of the principles seemed quite obvious because I was already applying similar approaches in practice. But some of them made me look at class design and responsibility assignment from a slightly different perspective.
And I think this is where the real value of GRASP lies.
GRASP is not really about telling us "write your code this way." Instead, it helps us answer a much more important question:
Who should be responsible for this?
And this is a question we constantly face when designing software.
What is GRASP?
GRASP stands for General Responsibility Assignment Software Patterns.
It is a set of nine principles that help us decide how responsibilities should be distributed between objects.
The nine GRASP principles are:
- Information Expert
- Creator
- Controller
- Low Coupling
- High Cohesion
- Polymorphism
- Pure Fabrication
- Indirection
- Protected Variations
At first, this may look like another list of rules that we simply need to memorize.
I don't think that's the best way to approach GRASP.
I prefer to think of GRASP as a set of different ways to look at the same design problem.
We have a new responsibility. Who should handle it?
Will this create too much coupling?
Will this make the class too large?
What happens if this part of the system changes?
Do we need another object or abstraction here?
And this is where GRASP becomes really useful.
GRASP and SOLID — are they the same thing?
No.
But there is a strong relationship between them.
SOLID gives us general principles for designing good object-oriented software.
For example:
- a class should have a focused responsibility;
- dependencies should preferably point toward abstractions;
- implementations should be replaceable;
- high-level code should not depend directly on low-level details.
GRASP is more focused on the actual design decisions we make when assigning responsibilities.
For example, suppose we have:
class Order { private array $items; }
And we need to calculate the total price of the order.
Where should this logic go?
We could create:
class OrderService { public function calculateTotal(Order $order) { // ... } }
But why OrderService?
The Order already contains the information we need: its items, prices, and quantities.
So it is quite natural for the Order itself to calculate its total:
$order->total();
This is Information Expert.
Then we can look at the same decision from other perspectives:
- Does it create unnecessary coupling?
- Does it keep the class cohesive?
- Is this responsibility really part of the
Order?
So GRASP helps us reason about design, while SOLID gives us a set of fundamental principles for evaluating that design.
There is also a lot of overlap between them.
For example:
OrderService
↓
PaymentGateway
↑
│
StripeGatewayThe same design can be viewed from several GRASP perspectives:
Polymorphism:
StripeGateway and PayPalGateway implement the same contract.
Protected Variations:
We protect OrderService from changes in the particular payment provider.
Indirection:
PaymentGateway acts as an intermediary between OrderService and the concrete implementation.
Low Coupling:
OrderService no longer directly depends on Stripe.
And that's perfectly fine.
GRASP principles are not mutually exclusive.
1. Information Expert
This is probably one of the most intuitive principles.
The idea is simple:
Assign a responsibility to the object that has the information needed to fulfill it.
Suppose we have:
class Order { private array $items; }
Each OrderItem contains a price and quantity.
We need to calculate the total price.
Where should we do it?
We could put it in:
class OrderService { public function calculateTotal(Order $order) { // ... } }
But the Order already has all the necessary information.
So it makes sense to write:
$order->total();
The Order is the Information Expert.
However, this principle doesn't mean:
"If a class has some data, all operations involving that data must be placed in that class."
That would be an overly simplistic interpretation.
For example, Order may contain a shipping address and the total weight of the order.
That doesn't mean Order should call a shipping company's API itself.
We still need to consider other things:
- coupling;
- cohesion;
- separation of responsibilities;
- infrastructure dependencies.
So Information Expert is better understood as a starting point for finding the right owner of a responsibility, rather than an absolute rule.
2. Creator
The next question is:
Who should create this object?
Suppose an Order consists of OrderItem objects.
Who should create an OrderItem?
$order->addItem($product, $quantity);
Here, Order is a good candidate because:
OrdercontainsOrderItem;Orderuses it;Orderaggregates these objects;OrderItemis logically a part ofOrder.
There is already a strong relationship between the two objects.
It's important to understand that Creator doesn't necessarily mean that the class must literally use new.
The object might be created through a factory or some other mechanism.
For example:
$order->addItem(...);
could internally use a factory.
The important question is not:
"Where is
newwritten?"
The important question is:
Which object logically owns the responsibility of creating this object?
This becomes particularly interesting in Laravel, where many dependencies are created by the service container.
So Creator is not necessarily about the new keyword.
It is about assigning the creation responsibility to an appropriate object.
3. Controller
This principle can easily be confused with the MVC Controller.
In Laravel, we are used to controllers such as:
class OrderController { public function store(StoreOrderRequest $request) { // ... } }
But GRASP Controller and MVC Controller are not exactly the same concept.
In GRASP, a Controller is an object that receives a system event from the outside world and delegates the operation to the appropriate part of the system.
HTTP request
↓
OrderController
↓
OrderService
↓
DomainThe Controller should not become a place where all business logic ends up:
public function store(...) { // validate // calculate price // create order // save order // call payment API // send email // ... }
This kind of controller quickly becomes a God Object.
Its main job is to receive the request and initiate the appropriate system operation.
For example:
public function store(StoreOrderRequest $request) { return $this->orderService->create( $request->validated() ); }
So a Controller is basically a point of entry into the system, not a place where we should put all the business logic.
4. Low Coupling
Coupling describes how strongly one component depends on other components.
For example:
class OrderService { public function create(Order $order) { $stripe = new StripePayment(); $stripe->pay($order); } }
Now we have:
OrderService → StripePayment
OrderService directly knows about Stripe.
If we want to add PayPal, we have to modify OrderService.
A better approach might be:
class OrderService { public function __construct( private PaymentGateway $paymentGateway ) {} public function pay(Order $order) { return $this->paymentGateway->pay($order); } }
Now we have:
OrderService
↓
PaymentGateway
↑
┌────┴─────┐
Stripe PayPalOrderService no longer depends on a specific payment provider.
But there is an important distinction here.
Dependency Injection and Low Coupling are not the same thing.
Dependency Injection is a mechanism for providing dependencies.
Low Coupling is a design goal:
Avoid unnecessary and overly strong dependencies between components.
Dependency Injection can help us achieve that goal, but using DI does not automatically mean that the system has low coupling.
5. High Cohesion
If Low Coupling is about the relationships between components, Cohesion is about what happens inside a component.
A simple question is:
How closely related are the responsibilities of this class?
For example:
class OrderService { public function calculateTotal() {} public function saveOrder() {} public function sendEmail() {} public function generatePdf() {} public function resizeProductImage() {} public function callPaymentApi() {} }
All of these methods might somehow be related to an order.
But the class is doing too many unrelated things.
It has low cohesion.
A better design might be:
Order └── total() OrderRepository └── save() OrderMailer └── send() InvoiceGenerator └── generate() PaymentService └── pay()
Now each component has a much more focused set of responsibilities.
This is also where the relationship between GRASP and SOLID becomes clear.
High Cohesion is closely related to the idea behind the Single Responsibility Principle.
But I wouldn't consider them exactly the same thing.
They approach the design problem from slightly different perspectives.
6. Polymorphism
This principle is probably familiar to almost every developer.
Suppose we have several payment providers:
Stripe PayPal LiqPay Fondy
We can define a common contract:
interface PaymentGateway { public function pay(Order $order): PaymentResult; }
And then create different implementations:
class StripeGateway implements PaymentGateway { // ... } class PayPalGateway implements PaymentGateway { // ... }
Now the code that performs the payment can work with the abstraction:
PaymentGateway $gateway
It doesn't need to know which concrete implementation is being used.
This is where Polymorphism comes into play.
Different objects follow the same contract but provide different implementations of the behavior.
Again, there is a clear connection with SOLID, especially the Open/Closed Principle and Dependency Inversion Principle.
For example:
PaymentGateway
↑
┌─────┴─────┐
Stripe PayPalWe can add another implementation without changing the code that works with PaymentGateway.
But this doesn't mean that we should create an interface for every class.
If there is only one implementation and there is no real reason to expect variations, adding an interface may simply make the code more complicated.
7. Pure Fabrication
This is one of the principles I found particularly interesting.
It asks:
What should we do when no domain object is a good place for a particular responsibility?
In that case, we can create an artificial software object that doesn't actually exist in the real-world domain.
Suppose our domain contains:
Order Customer Product Payment
These are real concepts in our application.
But we need to save an Order to the database.
We could put this directly into Order:
class Order { public function save() { // SQL } }
But then Order becomes responsible for both business logic and persistence.
Instead, we can create:
class OrderRepository { public function save(Order $order) { // ... } }
There is no OrderRepository in the real-world domain.
We invented it for the software.
And that's not a problem.
In fact, it can be a very good architectural decision.
The same idea can be applied to classes such as:
PaymentService OrderMailer InvoiceGenerator FreeDeliveryService OrderRepository
These objects may not exist in the business domain, but they can still be very useful in our software design.
At the same time, Pure Fabrication doesn't mean:
"Let's put everything into a Service."
If Order is naturally responsible for an operation, there may be no reason to create another service.
This is why Pure Fabrication works well together with Information Expert:
First, look for a natural place for the responsibility. If there isn't one, or if putting it there leads to a poor design, create a separate software object.
8. Indirection
The name of this principle may sound a little strange at first.
The idea is actually quite simple:
If two objects are too tightly connected, introduce an intermediary between them.
Instead of:
A → B
we get:
A → C → B
For example:
OrderService → Stripe
can become:
OrderService → PaymentGateway → Stripe
PaymentGateway becomes the intermediary.
Another example is using events:
OrderService
↓
OrderCreated
↓
Listener
↓
EmailNow OrderService doesn't need to know that an email has to be sent after an order is created.
At first glance, adding an intermediate object may seem like unnecessary complexity.
But sometimes this extra layer is exactly what allows us to reduce coupling and isolate components from each other.
And again, several GRASP principles can be present in the same solution.
A PaymentGateway, for example, can simultaneously represent:
- Indirection — it acts as an intermediary;
- Polymorphism — it provides a common contract for different implementations;
- Protected Variations — it protects us from changes in the payment provider;
- Low Coupling — it removes a direct dependency on Stripe.
This is perfectly normal.
GRASP is not a collection of nine isolated boxes.
9. Protected Variations
This principle is, in my opinion, one of the most interesting ones when it comes to architecture.
The main idea is:
Identify what is likely to change and protect the rest of the system from that variation.
Suppose our application uses Stripe:
OrderService → Stripe
Today it's Stripe.
Tomorrow the business says:
We need to add PayPal.
Later:
Let's add LiqPay.
Then:
We also need Fondy.
If concrete payment providers are used directly throughout the application, changes start spreading across the codebase.
So we identify the point of variation:
Payment Provider
and create a stable abstraction around it:
interface PaymentGateway { public function pay(Order $order): PaymentResult; }
Now we have:
Stripe
↑
OrderService → PaymentGateway
↑
PayPalOrderService is now protected from changes in the particular payment provider.
This is Protected Variations.
There are two useful concepts here.
Point of Variation is the place where variation exists or is expected.
Stripe / PayPal / LiqPay
Point of Protection is the place that isolates the rest of the system from that variation:
PaymentGateway
And again, there is a strong connection with SOLID, especially Dependency Inversion.
But the motivation is slightly different.
DIP tells us:
Don't make high-level code depend directly on low-level details.
Protected Variations makes us ask:
What part of the system is likely to change, and how can I protect the rest of the system from that change?
How these principles work together
I think this is where things become really interesting.
Suppose we have an e-commerce application and need to implement order payment.
We can start reasoning about the design step by step.
Step 1. Who should know how to calculate the order total?
Order.
This is Information Expert.
$order->total();
Step 2. Who should create an OrderItem?
Order, because OrderItem is part of it.
This is Creator.
Step 3. Who receives the HTTP request?
OrderController.
This is Controller.
Step 4. Who saves the order?
We can create an OrderRepository.
This is Pure Fabrication.
Step 5. How do we avoid making OrderService depend directly on Stripe?
Use PaymentGateway.
This helps achieve Low Coupling.
Step 6. How do we avoid making Order too large?
Don't put persistence, email, external integrations, and infrastructure logic into it.
This is where High Cohesion helps.
Step 7. How do we work with Stripe and PayPal in the same way?
Use a common contract with different implementations.
This is Polymorphism.
Step 8. How do we separate OrderService from the concrete payment provider?
Introduce an abstraction between them.
This is Indirection.
Step 9. What if Stripe changes or another provider is added?
Protect the application code behind the stable PaymentGateway abstraction.
This is Protected Variations.
The result is not nine separate solutions.
It is one coherent design where several GRASP principles work together.
Don't learn GRASP as a list of rules
Of course, we can make a cheat sheet:
| Principle | Main question |
|---|---|
| Information Expert | Who has the information needed for this responsibility? |
| Creator | Who should create this object? |
| Controller | Who receives the system operation? |
| Low Coupling | How can we reduce unnecessary dependencies? |
| High Cohesion | How well are the responsibilities related inside the class? |
| Polymorphism | How should we handle different variations of behavior? |
| Pure Fabrication | Should we create an artificial software object for this responsibility? |
| Indirection | Do we need an intermediary between these components? |
| Protected Variations | What change do we need to protect the system from? |
But I don't think memorizing this table is enough.
It is much more useful to get used to asking yourself these questions when designing software:
Who should be responsible for this operation?
Then:
Who has the information needed to perform it?
Then:
Will this create too much coupling?
Then:
Will this make the class too large?
Then:
Is there a part of the system that is likely to change?
Then:
Should the rest of the system be protected from that change?
And finally:
Do I need another object, abstraction, or intermediary?
That, for me, is the practical side of GRASP.
Why learn GRASP if we already have SOLID?
If I already know SOLID, why do I need GRASP?
For me, the answer is roughly this:
SOLID helps us understand what good design should look like.
GRASP helps us reason about how to get there.
For example, SOLID tells us:
Don't create classes with too many unrelated responsibilities.
GRASP helps us ask:
Who should take this responsibility instead?
SOLID tells us:
Don't depend directly on concrete implementations.
GRASP helps us ask:
What part of the system is likely to vary, and how should we isolate it?
SOLID tells us:
Use polymorphism where it makes sense.
GRASP helps us understand what kind of design problems polymorphism can solve.
So I don't see GRASP and SOLID as competing approaches.
I see them as complementary.
The main thing I took away from GRASP
Probably the most important idea I took from GRASP is that good software design is largely about assigning responsibilities correctly.
We constantly have to answer questions like:
Who should do this? Who should create this? Who should know about this? Who should depend on whom? What happens if this part changes?
GRASP gives us a set of useful ways to reason about these questions.
And the same piece of code can easily demonstrate several principles at the same time.
For example:
OrderService
↓
PaymentGateway
↑
StripeGatewayHere we can see Polymorphism, Indirection, Protected Variations, and Low Coupling at the same time.
And that's perfectly fine.
So I wouldn't try to classify every class by saying:
"This class is Polymorphism, and that one is Protected Variations."
I think it is much more useful to ask:
What problem am I solving right now, and why does this particular distribution of responsibilities make the system better?
And that's what makes GRASP much more interesting to me than just another list of design principles.

Add new comment