GRASP Principles
About
GRASP (General Responsibility Assignment Software Patterns) is a set of nine fundamental principles used in Object-Oriented Design (OOD) to guide responsibility assignment in software systems. These principles help determine which class should handle which responsibility, ensuring a well-structured, maintainable, and scalable design.
Why GRASP?
In Object-Oriented Design, one of the most critical decisions is how to assign responsibilities to classes and objects. GRASP provides a systematic approach to make these decisions based on best practices.
The 9 GRASP Principles
Principle
Description
1. Information Expert
Assign responsibility to the class that has the most information required to fulfil it.
2. Creator
The class that contains, aggregates, or closely uses another object should create it.
3. Controller
Assign system operations to a controller that handles incoming requests from the UI.
4. Low Coupling
Reduce dependencies between classes to improve maintainability.
5. High Cohesion
Keep related behavior within a single class to ensure clarity and reusability.
6. Polymorphism
Use interfaces or abstract classes to allow different implementations without modifying the calling code.
7. Pure Fabrication
Introduce a new class (not representing a real-world entity) to improve cohesion and reuse (e.g., a Service class).
8. Indirection
Use an intermediary to reduce direct coupling (e.g., a DAO class for database access).
9. Protected Variations
Use abstraction to protect the system from changes (e.g., Strategy Pattern, Factory Pattern).
1. Information Expert
Assign a responsibility to the class that has the necessary information to fulfill it.
Applicability:
If an object has direct access to the required data, it should handle the responsibility.
Helps to reduce dependencies and keep behavior close to the data.
Example:
A Student
object calculates its own GPA instead of an external class doing it.
class Student {
private List<Double> grades;
public double calculateGPA() {
double total = grades.stream().mapToDouble(Double::doubleValue).sum();
return total / grades.size();
}
}
Encapsulation is maintained: The data and logic stay in the same class.
2. Creator
Assign the responsibility of creating an object to a class that has the most logical reason to do so.
Applicability:
A class should create an instance of another class if:
It contains objects of that class.
It uses the created object heavily.
It has the necessary data to initialize the object.
Example:
A Customer
object creates an Order
object since it logically owns orders.
class Customer {
List<Order> orders = new ArrayList<>();
public Order createOrder() {
Order order = new Order();
orders.add(order);
return order;
}
}
3. Controller
Assign the responsibility of handling system events to a dedicated Controller class.
Applicability:
When designing the entry point of a system (UI layer, API, service layer).
A Controller class should delegate tasks to the relevant domain classes.
Example:
A BankController
class manages user transactions instead of the UI directly handling them.
class BankController {
private BankService bankService;
public void deposit(int accountId, double amount) {
bankService.deposit(accountId, amount);
}
}
Ensures separation of concerns between UI and business logic.
4. Low Coupling
Reduce dependencies between classes to improve maintainability and flexibility.
Applicability:
Avoid making changes in one class that require changes in multiple other classes.
Use dependency injection and interfaces instead of hard dependencies.
Example:
Using an interface instead of direct implementation in a service.
interface PaymentGateway {
void processPayment(double amount);
}
class PayPalPayment implements PaymentGateway {
public void processPayment(double amount) {
System.out.println("Processing via PayPal: $" + amount);
}
}
class Order {
private PaymentGateway paymentGateway;
public Order(PaymentGateway paymentGateway) {
this.paymentGateway = paymentGateway;
}
public void checkout(double amount) {
paymentGateway.processPayment(amount);
}
}
Low Coupling allows switching from PayPal to Other without modifying the Order
class.
5. High Cohesion
Ensure that a class focuses on a single responsibility and avoids doing unrelated tasks.
Applicability:
If a class handles too many responsibilities, break it into smaller classes.
Classes should group related functionality together.
Example:
Splitting User
and UserLogger
classes instead of mixing authentication & logging.
class User {
private String name;
private String email;
public void login(String password) {
// Login logic
}
}
class UserLogger {
public static void logLoginAttempt(User user) {
System.out.println("User logged in: " + user.getEmail());
}
}
High Cohesion ensures better readability and maintainability.
6. Polymorphism
Use interfaces and abstract classes to allow different implementations to be handled uniformly.
Applicability:
When multiple classes share common behaviour but differ in implementation.
Enables open-closed principle (adding new behaviour without modifying existing code).
Example:
Multiple payment methods implementing a common interface.
interface Payment {
void process(double amount);
}
class CreditCardPayment implements Payment {
public void process(double amount) {
System.out.println("Processing Credit Card payment: $" + amount);
}
}
class PayPalPayment implements Payment {
public void process(double amount) {
System.out.println("Processing PayPal payment: $" + amount);
}
}
class PaymentProcessor {
public void pay(Payment payment, double amount) {
payment.process(amount);
}
}
7. Pure Fabrication
Introduce a class that does not represent a real-world object but exists purely for better separation of concerns.
Applicability:
When behaviour doesn’t fit naturally into an existing class.
To reduce coupling and increase cohesion.
Example:
A Logger
class doesn’t represent a real-world entity but handles logging separately.
class Logger {
public static void log(String message) {
System.out.println("[LOG] " + message);
}
}
Keeps logging concerns separate from business logic. Allows adding new payment methods without modifying PaymentProcessor
.
8. Indirection
Use an intermediate class to mediate between components to avoid direct coupling.
Applicability:
When a mediator is needed to control interactions.
Used in event-driven architectures or dependency injection frameworks.
Example:
Using a MessageBus
instead of direct communication between modules.
class MessageBus {
public static void send(String message) {
System.out.println("Message sent: " + message);
}
}
class Order {
public void placeOrder() {
MessageBus.send("Order placed!");
}
}
Allows flexibility in handling notifications or event-driven design.
9. Protected Variations
Shield parts of a system from unwanted changes by using abstractions, encapsulation, and interfaces.
Applicability:
When system components should be extendable without modification.
Helps in following Open-Closed Principle (OCP).
Example:
Using a database interface instead of hardcoding database logic in services.
interface Database {
void connect();
}
class MySQLDatabase implements Database {
public void connect() {
System.out.println("Connected to MySQL");
}
}
class Application {
private Database database;
public Application(Database database) {
this.database = database;
}
public void start() {
database.connect();
}
}
Now we can switch databases without modifying Application
.
Last updated
Was this helpful?