One of the joys of blogging is that you occasionally discover people thoughtfully and politely reducing your arguments to shreds. I recently came across an article by William Caputo on the subject of my discussion with Ryan back in November.* I’ll try to summarize the original discussion:
- Ryan contended that using Python fundamentally changed the principles of OOP.
- I argued that the SOLID principles still held.
Now, in my original article, I accepted that dynamic languages helped ameliorate the sharp edges of statically typed languages. Importantly,
- Python’s constructor syntax means that any constructor is effectively an implicit abstract factory. (This advantage is unique to Python, Ruby is nowhere near as slick in this respect.)
- The dynamic nature of Python means that your interaction surface with another class is exactly those methods you call, no more no less.
Now, in certain aspects, I assumed that certain principles became less important simply because the language took some of the burden. William, however, has pointed out that I was wrong.*
The thing is, we were both concentrating on one aspect of SOLID here: statically typed languages have fairly high friction related to their type system that can render code brittle. We therefore have practices closely associated with SOLID principles that are pretty much the only way to keep code flexible in languages in C#. These practices, such as always creating an interface to go with an implementation, are themselves a form of friction which Ryan was arguing was unnecessary in Python.
As William points out, that’s a good benefit of SOLID; it’s not the whole story.
ISP Isn’t About Code
Imagine you’ve got a space station. This station gets visited by two kinds of ships: shuttles, which carry people, and refuelling tankers. Now, the requirements for the shuttle’s docking interface are quite large: you’ve got to be able to comfortably get a stable human shaped hole between the two for an extended period of time. Refuelling, on the other hand, is carried out by attaching a pipe to the tanker.
Now imagine that you were told that both ships needed to use the same connector. You’d end up with a massively overcomplex connector. Now, this metaphor works perfectly well if you consider the space station to be exposing a single IConnector interface and the ships to be consuming classes. However, William’s first point is that actually, it still holds for data feeds, web services, any interaction between two systems. Indeed, the ISP does in fact, apply to space stations. In many ways, interfaces are cheap in code. But in third party integration, it’s expensive and so the ISP is more important. Something to bear in mind the next time you try to reuse the webservice you built for the last client.
Just Because You Can, Doesn’t Mean You Should
Since I’m interviewing at the moment, I’m getting heartily sick of hearing the phrase “an interface is a contract”, but it’s relevant in this context. In a statically typed language the contract is fixed and enforced by the consumed class. Because of this friction, often you get an interface that is larger than it should be because it’s trying to be forgiving enough to handle multiple clients. ISP says you should be doing the opposite: having interfaces for each consumer. In a dynamic language, the consumed class can’t enforce the contract. However, that doesn’t remove the concern, it just rebalances the responsibilities.
Returning to the space station, imagine if you allowed a ship to attach itself to any part of the hull. That would certainly help with adding in new types of vessel to the mix. The problem would come when you wanted to change the space station itself. Maybe those solar panels aren’t very useful anymore and you’d like to get rid of them. Unfortunately, it turns out that there’s a visiting space monster that wraps its tentacles around the panels. You don’t want to upset the monster, so you end up leaving the useless panels on the station.
This is the danger in dynamic languages. In a statically typed language, the space monster wouldn’t have been able to visit at all without work on part of the station. However, if we observe the ISP, we still have to do the work. Equally, the space monster needs to be responsible and not just attach itself to anything that provides purchase. To put it more formally, the consumed class still needs to export an interface the consuming class is going to find useful, and the consuming class has avoid taking unnecessary dependencies. The expression of the problem may be different, but the concerns and the principle remains.
I originally said that because Python automatically keeps interface surfaces as small as the developer is actually using there wasn’t much you could do about ISP in Python, but in fact that’s not the case. Interaction interfaces between classes can still be made smaller, they can still be made more role specific. You can still attempt to create Unified Modelzilla in Python, and it will be as bad an idea as it was when you tried it in J2EE. In many ways, paying attention to ISP is more important in Python than it is with a statically typed language.
*If you want to read it, William’s article is on his home page dated 21 November. I’m afraid I don’t have a permalink.