Why GRASP Principles are Essential for Effective Object-Oriented Design

Anh Trần Tuấn - Oct 30 - - Dev Community

1. What is GRASP?

Image

GRASP stands for General Responsibility Assignment Software Patterns, a set of nine principles designed to guide the assignment of responsibilities in object-oriented software design. These principles aim to ensure that software is both flexible and maintainable.

1.1 The Importance of GRASP

Adopting GRASP principles helps developers create systems that are easier to understand, modify, and extend. It reduces the risk of creating tightly coupled systems, which are difficult to maintain and evolve over time.

Image

The eight GRASP principles are:

  • Information Expert
  • Creator
  • Controller
  • Low Coupling
  • High Cohesion
  • Polymorphism
  • Pure Fabrication
  • Indirection

Each principle focuses on a specific aspect of responsibility assignment, which we will explore in detail.

2. Deep Dive into GRASP Principles

2.1 Information Expert

Assign responsibility to the class that has the information necessary to fulfill it.

Example : Imagine a system where you need to calculate the total price of items in a shopping cart. The ShoppingCart class should be responsible for this calculation since it contains the list of items and their prices.

public class ShoppingCart {
    private List<Item> items;

    public double calculateTotal() {
        double total = 0.0;
        for (Item item : items) {
            total += item.getPrice();
        }
        return total;
    }
}
Enter fullscreen mode Exit fullscreen mode

This approach ensures that the logic for calculating the total is centralized in the class that knows the most about the items in the cart, adhering to the Information Expert principle.

2.2 Creator

Assign responsibility to the class that has the information needed to create an instance of another class

Example : A Customer class might be responsible for creating instances of Order because it knows the customer’s details necessary for an order.

public class Customer {
    private String name;
    private String address;

    public Order createOrder() {
        return new Order(this);
    }
}
Enter fullscreen mode Exit fullscreen mode

The Customer class is naturally suited to create Order instances because it possesses the necessary details, following the Creator principle.

2.3 Controller

Assign responsibility to a class that represents an overall system, a subsystem, or a use-case scenario.

Example : In a banking application, an AccountController might handle requests for withdrawing and depositing money.

public class AccountController {
    private AccountService accountService;

    public void deposit(int accountId, double amount) {
        accountService.deposit(accountId, amount);
    }

    public void withdraw(int accountId, double amount) {
        accountService.withdraw(accountId, amount);
    }
}
Enter fullscreen mode Exit fullscreen mode

Demo Result : The AccountController manages the flow of activities related to accounts, ensuring that the system remains organized and maintainable.

2.4 Low Coupling

Assign responsibilities so that coupling between classes is low, which means that changes in one class will not lead to significant changes in other classes.

Example : Using interfaces to reduce dependencies between classes.

public interface PaymentService {
    void processPayment(double amount);
}

public class CreditCardPaymentService implements PaymentService {
    public void processPayment(double amount) {
        // Process credit card payment
    }
}

public class Order {
    private PaymentService paymentService;

    public Order(PaymentService paymentService) {
        this.paymentService = paymentService;
    }

    public void checkout() {
        paymentService.processPayment(100.0);
    }
}
Enter fullscreen mode Exit fullscreen mode

Demo Result : The Order class relies on the PaymentService interface rather than a specific implementation, minimizing coupling and making the code easier to maintain and extend.

2.5 High Cohesion

Ensure that a class is focused on a single responsibility, leading to higher cohesion.

Example : A Customer class should focus solely on customer-related responsibilities.

public class Customer {
    private String name;
    private String address;

    public String getName() {
        return name;
    }

    public String getAddress() {
        return address;
    }
}
Enter fullscreen mode Exit fullscreen mode

Demo Result : The Customer class only handles customer data, adhering to the principle of high cohesion.

2.6 Polymorphism

Use polymorphism to handle variations in behavior based on type.

Example : Payment processing can vary by payment method (credit card, PayPal, etc.).

public interface PaymentMethod {
    void pay(double amount);
}

public class CreditCardPayment implements PaymentMethod {
    public void pay(double amount) {
        // Process credit card payment
    }
}

public class PayPalPayment implements PaymentMethod {
    public void pay(double amount) {
        // Process PayPal payment
    }
}
Enter fullscreen mode Exit fullscreen mode

By using polymorphism, the system can easily accommodate new payment methods without altering existing code.

2.7 Pure Fabrication

Assign responsibility to a class that doesn’t represent a concept in the problem domain but is needed for system design.

Example : A LoggingService class that handles logging throughout the application.

public class LoggingService {
    public void log(String message) {
        // Log the message
    }
}
Enter fullscreen mode Exit fullscreen mode

The LoggingService doesn’t correspond to any real-world entity, but it’s necessary for clean and maintainable code.

2.8 Indirection

Assign responsibility to an intermediate object to mediate between other components or services.

Example : Using a PaymentGateway class to handle communication between Order and payment processing services.

public class PaymentGateway {
    public void process(PaymentMethod paymentMethod, double amount) {
        paymentMethod.pay(amount);
    }
}
Enter fullscreen mode Exit fullscreen mode

The PaymentGateway acts as an intermediary, reducing direct dependencies and making the system more flexible.

3. Conclusion

The GRASP principles provide a solid foundation for effective object-oriented design. By following these principles, you can build systems that are robust, maintainable, and scalable. Each principle addresses a specific aspect of responsibility assignment, helping to create a well-structured and flexible design.

Feel free to leave a comment below if you have any questions or if you’d like to share your thoughts on how you’ve applied GRASP principles in your projects!

Read posts more at : Why GRASP Principles are Essential for Effective Object-Oriented Design

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .