People that know me will tell you I am a big fan of the SOLID Design Principles championed by Robert C. Martin (Uncle Bob). Over the years I've use...
For further actions, you may consider blocking this person and/or reporting abuse
I am not sure if I understand the example used for LSP.
You may have noticed all of the FTP client classes so far have the same function signatures. That was done purposefully so they would follow Liskov's Substituitability Principle. An SFTPClient object can replace an FTPClient object and whatever code is calling upload, or download, is blissfully unaware.
SFTPClient requires username and password, whereas FTPClient does not. In the calling code I cannot replace FTPClient(host=host, port=port) with SFTPClient(host=host, port=port), so I do not see how SFTPClient can be a substitute for FTPClient. Could you please explain?
Great question! I had to look up Liskov's again to make sure this example was still valid. Fortunately for me, and quite by accident, I think it still works. Let me try to explain.
Liskov's as it originally appeared states, "Let Φ(x) be a property provable about objects x of type T. Then Φ(y) should be true for objects y of type S where S is a subtype of T." So I took this as the principle applies to objects, not classes. In general, I think most people agree. So a class has a constructor which returns an object and as long as the object returned by the constructor can replace an object of the parent type you are following Liskov's.
All that being said with higher-level languages like Python it's probably a better practice to follow Liskov's even with the constructor since we can pass a Class name to a function and then construct an object of that class inside the function. In that scenario what I've written in this article would absolutely blow up.
This is actually my biggest issue with SOLID. Having to look it up. After reading the Zen of Python at the top of the article, it feels easier because it's a mindset.
Thank you for the explanation!
Thanks for pointing it out. I've fixed it.
Nicely done!
Also, I think
IFTPClient
is a violation of SRP in this case. You should simply take in both separately(ICanUpload uploader, ICanDownload downloader)
.On single responsibility:
If one class/function/method does X on L or X on M or X on N then it seems the single responsibility could be "do X" rather than splitting it to 3 separate places of L. M. and N handling.
If a lead uses the principle to insist others change code then it could cause friction if it is not convincingly argued why a view on responsibility is to be preferred.
You've hit the nail on the head. I tried to enforce SOLID design in a code base where I was not a team lead. It caused a lot of friction because I didn't explain the principle or benefits well. I hope this article helps with insufficient explanations, but I don't think friction is inherently bad. As a team lead you should be pushing your developers to be even better, but sometimes that means letting them make a mistake and learn from it. The really good team leads will be able to find that balance.
I'm curious about the X on L or X or M, or X on N explanations you offered though. I don't quite follow what you are getting at, but if you have a more concrete example I'd love to discuss it further.
I believe that what Paddy3118 meant with the X on L or X or M, or X on N explanations is that sometimes developers may have different opinions on what a single responsibility constitutes. In the end ...
There should be one-- and preferably only one --obvious way to do it.
Although that way may not be obvious at first unless you're Dutch.
I typically find that after discussing the various possibilities with my team and figuring out the pros and cons of different implementations, the obvious best way will arise.
Ay, someone else may have thought through wider implications, or not 🤔
What do you think about dependency injecting the FTPDriver itself so that the IO (establishing connection) isn't in the constructor? Example:
This allows one to install a
TestDriver
for testingFTPClient
. and bubbles up IO to an outer layer which uncle bob is big on (blog.cleancoder.com/uncle-bob/2012...), but we get to do it in one class because Python supports classmethods.It's an excellent practice. I left it out so the examples were more concrete and demonstrated the principles as simply as possible.
On the open closed principle
It seems useful on an established large project. But they are only a small subset of Python projects. There is also "plan to throw one away; you will, anyhow", which I use as Python is very good at exploring the solution space, in which OCP may be suppressed.
I love that, "plan to throw one away; you will, anyhow". When prototyping you should absolutely be ready to throw code away. I've been caught in the sunk cost fallacy plenty of times when I would have been better throwing things away.
Writing SOLID code is a discipline and the more often you practice it the easier it becomes. The entire FTP example I used for this article was born from just practicing writing SOLID code. I didn't intend for every example to build of the previous but going through the act of writing the code to adhere to each principle revealed an elegant solution I didn't even know was there.
Appreciate your time, and efforts to teach what you have learnt!
I just have a quick question, how would you approach SOLID design principle for this following problem..
I will send a directory path to your class, your class should perform following steps:
1, connect to the remote path (can be sftp, ftp, local path, or anything else in the future)
2, Reach the directory & read directory contents
3, for each file, detect the format and apply appropriate extraction method for texts.
4, update the DB
5, index in the ES
Finally, send me back the bool signal (success/ fail)
If you could, please share your abstract-level idea, that will be greatly helpful for me easily pick up this SOLID principle stuff. Thanks!
Hi Derek and thanks for this article!
You mentioned changing parameter names in the function signature adheres to the OCP. I was wondering how it works when the users of the API which we want to keep 'Closed' for change, explicitly state the parameter names when calling the APIs (for example: calling ftpCilent.upload(file=file_to_be_uploaded). I would assume that in that kind of coding style (which IMHO adds a lot to code readability) OCP may also require that parameter names are kept intact. What is your take on this?
This is a good point. It should state changing positional parameter names is still valid within OCP. Keyword args (kwargs) require the caller knowing the internal variable names of a function so changing those is not valid within OCP. I don't like my calling code knowing what's going on in my functions so I don't use them often which is probably why I missed this scenario.
Hi, thanks, this is a good article. It is interesting to see how SOLID can integrate into the python philosophy.
Great article! 👏 It was super easy to follow with the FTP example.
I recently moved from working with Java to Python and it's great to know that you can still apply SOLID principles with Python.
Great article!
How about adding @abc.abstractmethod to the abstract methods, which will enforce the implementation in the concrete class.
I debated using
abc
to enforce the implementations and went as far as to explain the Dreaded Diamond problem in C# and Java and how interfaces solve the problem in my first iteration of this article. In the end, I chose not to useabc
so it wouldn't confuse readers users into thinking@abc.abstractmethod
were required for ISP.I personally do use them and would recommend them, especially while teaching the principle to others.
I didn't know about that module. Thanks for the tip. I look forward to playing around with it.
Typo in intro to ISP: "complicated is better than complex" - the Zen of Python has it the other way around. Doesn't detract from a great article :)
Definitely, something I want to fix though. Thanks for pointing it out.