Summary of SOLID Principles
This is a summary of SOLID Principles explained in blog mentioned below.
What is SOLID Design
- SOILD design comes from paper Design Principles and Design Patterns by Robert C. Martin
- This mnemonic SOLID is attributed to Michael Feathers
- SOLID principle are intended to foster simpler, more robust and updatable code from software developers.
- The principles are:
- Single Resposibility Principle SRP
- Open Closed Principle
- Liskov’s Substitutabilty Principle
- Interface Segregation Principle
- Dependency Inversion Principle
- SOLID design principles are meant to be used holistically to get the real value out of it.
Single Responsibility Principle (SRP)
- Definition: Every module/class should only have one responsibility and therefore only one reason to change.
- Relevant Zen: There should be one– and preferably only one –obvious way to do things.
-
Notes:
- It increases cohesion and loose coupling by organizing code around responsibilities.
- Think of reponsibilities as use cases.
- Each use case should only be handled in one place therefore creating one obvious way to do things (following the Zen) hence “one reason to change” only if use case has changed (following SRP).
- The code is sparse, and not dense, simple not complex, flat and not nested with SRP
- From the example, the original class
FPTClienthas two resposibilities (handles two use cases),FTPandSFTPserver, therefore two reason to change the class. Splitting theFPTClientclass into 2 classes to handle each server separately fixes the issue.
Open Closed Principle (OCP)
- Definition: Software Entities (classes, functions, modules) should be open for extension but closed to change.
- Relevant Zen: Simple is better than complex. Complex is better than complicated.
-
Notes:
- Think of Change and Extension in term of function signatures.
- Forcing calling code to be updated is the Change, includes chaging function name, swapping the order of parameter or adding a non-default parameter.
- Not forcing calling code to be updated while providing new fuctionality, includes renaming a parameter, adding a new parameter with a default value, or adding
args, orkwarg(relates to Effective python, Item 23: Provide Optional Behavior with Keyword Arguments) - In terms of classes, changing methods signatures is the Change and adding new method is the Extension.
- From the example, adding
upload_bulkanddownload_bulkto theFTPClientextends the class, satisfying the OCP. This also satisfies SRP (only one reason to change theFTPClient, i.e., support for bulk download and upload). Although we can createBulkFTPCLientinheriting fromFTPClientbut for that we have to change the singnature fordownloadanduploadmethod as it would return list of bytes instead of just bytes therefore voilating the OCP as well as LSP.
Liskov’s Substituitability Principle (LSP)
- Definition: If S is a subtype of T, then objects of type T may be replaced with objects of Type S.
- Relevant Zen: Special cases aren’t special enough to break the rules.
-
Notes:
- This principle says that “Any child class can replace it’s parent class without breaking the functionality”.
- From the example,
SFTPClientobject can replace theFTPClientobject, meaning the calling code won’t be aware of callinguploadanddownloadmethods ofSFPTClientorFTPClient. Take an another case of specialized case of FTP file transfer is FTPS (different from SFTP), creating a new classFTPSClientthat extendsFTPClientis the way to go.
Interface Segregation Principle (ISP)
- Definition: A client should not depend on methods it does not use.
- Relevant Zen: Readability Counts && complicated is better than complex.
-
Notes:
- It’s about making reasonable choices for how other developers will interface with your code.
- A good interface will have good abstraction making the code more readable (follows the Zen).
- from the example, Since
S3not a special case ofFTP. We create aFileTransferCLientabstract class (it’s closest thing to interface in python) with methoddownloadandupload, since thatS3andFTPhave in common, the file transfer protocol. Similarly we can haveBulkFileTransferClientabstract class. So, any functions can be generic by operating onFileTransferClientinstead of coulping with specific client likeFPTClientorS3Client. - Example code:
class FileTransferClient(ABC): def upload(self, file:bytes): pass def download(self, target:str) -> bytes: pass
Dependency Inversion Principle (DIP)
- Definition: High-level modules should not depend on low-level modules. They should depend on abstractions and abstractions should not depend on details, rather details should depend on abstractions.
- Relevant Zen: Explicit is Better than Implicit
-
Notes:
- DIP ties all principles together. SOLID principles was all about getting to a place where we are no longer dependent on a detail.
- Dependency should be on abstractions not concretization.
- Our high-level modules no longer need to depend on a low-level module like
FTPCleint,SFTPClient, orS3Clent, instead, they depend on abstractionFileTransferClient. - Our abstraction
FileTransferClientis not dependent on protocol specific details and instead, those details depend on how they will be used through the abstraction (i.e. files can be uploaded or downloaded) - We can now write code around our business rules without tying them to a specific implementation.
- example code:
def exchange(client:FileTransferClient, to_upload:bytes, to_download:str) -> bytes: client.upload(to_upload) return client.download(to_download) if __name__ == '__main__': ftp = FTPClient('ftp.host.com') sftp = FTPSClient('sftp.host.com', 22) ftps = SFTPClient('ftps.host.com', 990, 'ftps_user', 'P@ssw0rd1!') s3 = S3Client('ftp.host.com') scp = SCPClient('ftp.host.com') for client in [ftp, sftp, ftps, s3, scp]: exchange(client, b'Hello', 'greeting.txt')- High-level function
exchangedepends on abstractionFileTransferClientand all the clients implementation depends on it too.
- High-level function