~/src/www.mokhan.ca/xlgmokha [main]
cat studying-object-oriented-programming.md
studying-object-oriented-programming.md 12949 bytes | 2008-05-30 00:00
symlink: /opt/dotnet/studying-object-oriented-programming.md

GRASP Patterns - Essential Object-Oriented Design Principles

I’m currently working through Craig Larman’s Applying UML and Patterns: An Introduction to Object-Oriented Analysis and Design and Iterative Development (3rd Edition), and while the early chapters on UML and RUP were admittedly dry, Chapter 16 on GRASP patterns has proven to be a goldmine of practical OOD wisdom.

Domain Modeling Fundamentals

Before diving into GRASP, Larman addresses a fundamental mistake in domain modeling:

“Perhaps the most common mistake when creating a domain model is to represent something as an attribute when it should have been a concept. A rule of thumb to help prevent this mistake is: If we do not think of some conceptual class X as a number or text in the real world, X is probably a conceptual class, not an attribute

This insight transforms how we approach domain modeling. Consider these examples:

Poor Design:

public class Order 
{
    public string CustomerName { get; set; }  // Should be a Customer object
    public string ProductName { get; set; }   // Should be a Product object
    public decimal Price { get; set; }        // Should be part of Product
}

Better Design:

public class Order 
{
    public Customer Customer { get; set; }
    public List<OrderLine> OrderLines { get; set; }
}

public class Customer 
{
    public string Name { get; set; }
    public Address Address { get; set; }
}

The Role of Domain Models

Larman defines the domain model’s purpose:

“The Domain Model provides a visual dictionary of the domain vocabulary and concepts from which to draw inspiration for the naming of some things in the software design.”

This isn’t just documentation - it’s the foundation for maintainable, expressive code that mirrors business concepts.

GRASP: General Responsibility Assignment Software Patterns

GRASP provides systematic guidance for assigning responsibilities to classes. Here are the five core patterns:

1. Information Expert

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

Example:

public class Order
{
    private List<OrderLine> orderLines;

    // Order has the information needed to calculate its total
    public decimal CalculateTotal()
    {
        return orderLines.Sum(line => line.GetSubtotal());
    }
}

2. Creator

Principle: Assign class B the responsibility to create instance A if:

  • B contains or aggregates A
  • B records A
  • B closely uses A
  • B has initializing data for A

Example:

public class Order 
{
    // Order creates OrderLine because it contains/aggregates them
    public void AddProduct(Product product, int quantity) 
    {
        var orderLine = new OrderLine(product, quantity);
        orderLines.Add(orderLine);
    }
}

3. High Cohesion

Principle: Keep responsibilities of a class strongly related and focused.

Poor Cohesion:

public class Order 
{
    public void CalculateTotal() { /* ... */ }
    public void SendEmail() { /* ... */ }        // Email responsibility
    public void UpdateInventory() { /* ... */ }   // Inventory responsibility
}

High Cohesion:

public class Order 
{
    public void CalculateTotal() { /* ... */ }
}

public class EmailService 
{
    public void SendOrderConfirmation(Order order) { /* ... */ }
}

public class InventoryService 
{
    public void UpdateStock(Order order) { /* ... */ }
}

4. Low Coupling

Principle: Minimize dependencies between classes.

Benefits:

  • Easier to understand in isolation
  • Easier to reuse
  • Less impact when changes occur
  • Easier to test

5. Controller

Principle: Assign responsibility for handling system events to a class representing:

  • The overall system
  • A use case scenario
  • The device that the software is running on

Example:

public class OrderProcessingController 
{
    private readonly IOrderService orderService;
    private readonly IPaymentService paymentService;

    public void ProcessOrder(OrderRequest request) 
    {
        // Coordinates the use case
        var order = orderService.CreateOrder(request);
        paymentService.ProcessPayment(order);
    }
}

Key Takeaways

  1. Think in objects, not data structures - Model real-world concepts as classes
  2. Follow responsibility-driven design - Ask “who should be responsible?” not “how do I do this?”
  3. Use GRASP patterns as design guidelines - They provide systematic approaches to common design decisions
  4. Balance cohesion and coupling - High cohesion within classes, low coupling between them

Practical Application

When designing classes, ask these questions:

  • What information does this class need to fulfill its responsibilities? (Information Expert)
  • What objects should this class create? (Creator)
  • Are this class’s responsibilities related? (High Cohesion)
  • How many other classes does this depend on? (Low Coupling)
  • Who coordinates this use case? (Controller)

GRASP transforms object-oriented design from art to systematic engineering, providing repeatable patterns for creating maintainable, well-structured code.