Nowadays, Object Oriented Programming (OOP) is the most commonly software design used by a huge numbers of software engineer and developers under the whole world. Most of us are familiar with several OOPs concepts as classes, objects, inheritance, etc. Did you have properly designed your software? Did your software is maintainable over time? Did your software is scalable? Many nightmares vanishes when our design follow SOLID design principles and best practices.
SOLID is an mnemonic acronym for five object-oriented design principles defined by Robert C, Martin (aka Uncle Bob):
- Single Responsibility Principle
- Open Closed Principle
- Liskov Substitution Principle
- Interface Segregation Principle
- Dependency Inversion Principle
THERE SHOULD NEVER BE MORE THAN ONE REASON FOR A CLASS TO CHANGE.
This means that a class should have one, and only one, responsibility: A class should do only one thing, because if a class have more than one responsibility, implementing a change would be more difficult than implementing the same on a class that have only one responsibility.
So let’s consider the following example that defines how activities are handled in an issue tracking system.
class ActivityHandler(object): def __init__(self, db, query_engine, email_sender): self.db = db self.query_engine = query_engine self.email_sender = email_sender def add_activity(self, activity): self.db.add(activity) def update_activity(self, activity): self.db.update(activity) def delete_activity(self, activity): self.db.delete(activity) def notify(self, activity): self.email_sender.send(str(activity), activity.user.email) def get_all_activities(self, user): return self.query_engine.run("SELECT * FROM activity WHERE assigned = " + user.id)
So, What’s do our class ?
- Basic CRUD operations, We could find in the near future that we should do validation on this methods
- listing jobs for a specific user, adding some enhancements like search, pagination, sort could be very complex operation
- Notifying user about activity
This is a lot of responsibilities for one class. Let’s design it properly: A good solution should be like that:
class ActivityRepository(object): def __init__(self, db): self.db = db # CRUD operations here def add_activity(self, activity): ... class ActivitySearchQuery(object): def __init__(self, query_engine): self.query_engine = query_engine def get_all_activities(self, user): return self.query_engine.run("SELECT * FROM activity WHERE assigned =" + user.id) class ActivityMonitor(object): def __init__(self, email_sender): self.email_sender = email_sender def notify(self, activity): self.email_sender.send(activity, activity.user.email)
So the advantage of SRP are:
- increase reusability
- decrease the cohesion between modules: To add a new requirement, fixbug, etc I had to make my changes in only one place.
- Classes are in understandable units
- avoid unwanted side-effects: 1 requirement ==> 1 place
- increase testability