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?

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?