S.O.L.I.D Principles
Why follow SOLID?
- Some problems without SOLID
- Mixed responsibility
- Missing responsibility
- Limited reuse potential
- Not substitutable
- Not extensible, not scalable
S.O.L.I.D
- Interface segregation
- Clients should not be forced to depend on methods they do not use. Helps design good classes.
- ISP is about making interfaces small so you don't force classes to do things they don't want to do.
- Helps write unit test cases.
- Example:
- Bad: An interface
LibraryActionsthat hascheckoutBook(),returnBook(), andrebindHardcover(). A "Regular Member" class is forced to implementrebindHardcover()even though they aren't allowed to fix books. - Good: Split the interface. Use
BorrowerActionsfor members andMaintenanceActionsfor the librarian. - Liskov substitution
- Subtypes must be substitutable for their base types. How subclasses extend superclasses.
- LSP is about maintaining the "Contract": It ensures that a subclass doesn't break the expectations set by its parent. It’s the rule that allows you to use Polymorphism safely.
- Liskov substitution = interface segregation + polymorphism
- Example:
- Bad: You have a base class
Birdwith afly()method. You create a subclassOstrich. Since an ostrich can't fly, you makefly()throw an error. Now, any code expecting aBirdmight crash if it gets anOstrich. - Good: Only put
fly()in aFlyingBirdsubclass. TheOstrichshould only inherit from a more generalBirdclass that doesn't assume flight. - Another example:
- The Violation: You have a
PhysicalBookclass with a methodgetShelfLocation(). You create a subclassEBook. Since an E-Book doesn't have a shelf, you makegetShelfLocation()return null or throw an Error. - Why it's bad: If a
Librarianobject loops through a list ofBooksand callsgetShelfLocation(), the code will crash or fail unexpectedly when it hits the E-Book. - The Fix: Don't put "Physical" properties in the base
Bookclass. - Open/closed
- Software entities should be open for extension, but closed for modification. Open for extension means adding subclasses when needed. Closed to modification avoids "tweaking" the code to handle new situations.
- OCP is about making code extensible so you never have to change your "core" logic when requirements change.
- Example:
- Bad: You have a
Discountclass with a methodapply(book). Every time you add a new discount type (Student, Senior, Black Friday), you have to go inside the apply method and add anif/elseblock. - Good: Create a
Discountinterface. Each new discount type becomes its own class (StudentDiscount,BlackFridayDiscount). You can add 100 new discounts without ever touching the original code. - Dependency inversion
- Depend on abstraction classes. Avoid concrete class name dependencies. A direct dependency on a concrete class needs to be "inverted"
- Example:
- Bad: A
NotificationServiceclass that creates a newEmailSender()inside its constructor. The notification service is now "stuck" with email. It can't send SMS without a code rewrite. - Good: The
NotificationServiceshould ask for aMessageSenderinterface. You can then "inject" anEmailSender,SmsSender, orWhatsAppSenderat runtime. - Single responsibility
- One responsibility per class. A class should have only one reason to change.
- Example:
- Bad: A
Bookclass that holds data (title, author) and also handles saving the book to a database. If the database schema changes, you have to change theBookclass. - Good: Use two classes:
Bookfor the data andBookRepositoryfor the database logic.
Other Object Oriented Principle
- Use Composition (has-a) over Inheritance (is-a) whenever necessary.
- Composition keeps classes independent. Use Inheritance when you want to achieve true Polymorphism: If you need to treat a group of different objects as the exact same type in a list or function.
- Don't repeat yourself (DRY)
- Every piece of knowledge or logic must have a single, unambiguous representation within a system. If you are copy-pasting code, you should probably move that logic into a function or a shared component (Composition).
- KISS (Keep it simple): avoid "over-engineering." Don't implement a complex Factory Pattern if a simple if statement will work for the foreseeable future.
- Law of Demeter (The Principle of Least Knowledge). A module should not know about the inner workings of the objects it manipulates.
Bad: paperboy.customer.wallet.takeMoney()(The paperboy is reaching into the customer's wallet).Good: customer.pay(paperboy)(The paperboy asks the customer, and the customer handles their own wallet).- General responsibility assignment software principles (GRASP)
Resources
- https://github.com/heykarimoff/solid.python/
- https://www.linkedin.com/learning/learning-s-o-l-i-d-programming-principles
- https://www.pythontutorial.net/python-oop/python-single-responsibility-principle/
- https://www.linkedin.com/pulse/solid-what-do-you-need-know-fred-peixoto-aovmf/?utm_source=share&utm_medium=member_android&utm_campaign=share_via