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
- Think in objects, not data structures - Model real-world concepts as classes
- Follow responsibility-driven design - Ask “who should be responsible?” not “how do I do this?”
- Use GRASP patterns as design guidelines - They provide systematic approaches to common design decisions
- 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.