Beyond SOLID: Designing Flexible Python Systems with GRASP
Overview: Why GRASP Matters for Python Developers
While many developers are familiar with the principles, the (General Responsibility Assignment Software Patterns) framework offers a more intuitive approach to object-oriented design. First popularized by in 1997, GRASP focuses on a fundamental question: who should do what? In , where we often mix object-oriented and functional styles, GRASP provides a pragmatic mental model for assigning responsibilities to classes and functions to ensure code remains maintainable and extensible.
Prerequisites
To get the most out of this tutorial, you should have a solid grasp of basic Python syntax, including classes and methods. Familiarity with concepts and the basic premise of is helpful but not required. We will be using Python 3.10+ features like Type Hinting and Protocols.
Key Libraries & Tools
- Python standard library: We utilize
abcfor abstract base classes andtypingfor . - Tkinter: Used for demonstrating the Controller principle in GUI applications.
- SQLite3: Used to demonstrate data persistence in high-cohesion examples.
Code Walkthrough: Assigning Responsibility

1. The Creator Principle
Deciding where to instantiate objects is a common hurdle. The Creator principle suggests that Class B should create Class A if B aggregates, contains, or closely uses A. This keeps the instantiation logic near the usage.
class Sale:
def __init__(self):
self.items = []
def add_line_item(self, product, quantity):
# Sale creates the line item because it 'owns' it
item = SaleLineItem(product, quantity)
self.items.append(item)
By moving the creation of SaleLineItem into Sale, we simplify the main execution logic. The caller no longer needs to know how to build a line item; they only need to know how to add a product to a sale.
2. Information Expert
This is perhaps the most useful principle for daily coding. You should assign a responsibility to the class that has the information necessary to fulfill it. If you need to calculate a total price, put that logic in the class that holds the list of items.
class Sale:
@property
def total_price(self) -> float:
return sum(item.price for item in self.items)
Instead of pulling data out of Sale to calculate the total elsewhere, we let Sale handle it because it is the "expert" on its own data.
3. Controller and Protected Variations
In GUI development, the Controller principle prevents your UI classes from becoming bloated "God Objects." By introducing a controller to handle system events, you separate the look of the app from its behavior. To make this even more robust, we use Protected Variations by defining an interface.
from typing import Protocol
class AppInterface(Protocol):
def update_product_list(self, products: list) -> None:
...
def add_product(self):
# The controller asks the view for data through the interface
name, price = self.view.ask_product_name_and_price()
self.model.add(name, price)
Using a allows the Controller to work with any UI framework—whether it's or —as long as the object implements the required methods.
Syntax Notes: Protocols vs. ABCs
In the examples above, we favor (Structural Subtyping) over (Nominal Subtyping). Protocols allow for "duck typing" with static type checking, which feels more natural in Python. When using a , you don't need to explicitly inherit from a base class; you just need to implement the expected methods.
Practical Examples
- E-commerce Systems: Use the Information Expert to handle discounts and shipping costs within the Order or Cart classes.
- Data Processing Pipelines: Use Pure Fabrication to create a "Logger" or "PersistenceManager" class that handles technical tasks that don't fit into the business domain.
- API Clients: Use Indirection to wrap third-party SDKs, allowing you to swap providers without breaking your core logic.
Tips & Gotchas
- The God Class Trap: If you follow Information Expert too rigidly, you might end up with a class that does everything. If a class gets too big, consider Pure Fabrication to split off technical concerns.
- Coupling vs. Cohesion: High cohesion and low coupling are two sides of the same coin. If you find yourself passing five or more arguments to a function, your coupling is likely too high, or your cohesion is too low.
- Refactor Constantly: Responsibility assignment isn't a one-time task. As your software evolves and new information is introduced, the "expert" might change. Don't be afraid to move methods between classes during refactoring.
- 31%· programming languages
- 15%· principles
- 8%· libraries
- 8%· people
- 8%· principles
- Other topics
- 31%

Apply the GRASP Design Principles to Improve Your Python Code
WatchArjanCodes // 32:02
On this channel, I post videos about programming and software design to help you take your coding skills to the next level. I'm an entrepreneur and a university lecturer in computer science, with more than 20 years of experience in software development and design. If you're a software developer and you want to improve your development skills, and learn more about programming in general, make sure to subscribe for helpful videos. I post a video here every Friday. If you have any suggestion for a topic you'd like me to cover, just leave a comment on any of my videos and I'll take it under consideration. Thanks for watching!