First of all, TDD does not strictly force you to write SOLID code. You could do TDD and create one big mess if you wanted to.
Of course, knowing SOLID principles helps, because otherwise you may just end up not having a good answer to many of your problems, and hence write bad code accompanied by bad tests.
If you already know about SOLID principles, TDD will encourage you to think about them and use them actively.
That said, it doesn't necessarily cover all of the letters in SOLID, but it strongly encourages and promotes you to write at least partly SOLID code, because it makes the consequences of not doing so immediately visible and annoying.
For example:
- You need to write decoupled code so you can mock what you need. This supports the Dependency Inversion Principle.
- You need to write tests that are clear and short so you won't have to change too much in the tests (which can become a large source of code noise if done otherwise). This supports the Single Responsibility Principle.
- This may be argued over, but the Interface Segregation Principle allows classes to depend on lighter interfaces that make mocking easier to follow and understand, because you don't have to ask "Why weren't these 5 methods mocked as well?", or even more importantly, you don't have a lot of choice when deciding which method to mock. This is good when you don't really want to go over the whole code of the class before you test it, and just use trial and error to get a basic understanding of how it works.
Adhering to the Open/Closed principle may well help tests that are written after the code, because it usually allows you to override external service calls in test classes that derive from the classes under test. In TDD I believe this is not as required as other principles, but I may be mistaken.
Adhering to the Liskov substitution rule is great if you want to minimize the changes for your class to receive an unsupported instance that just happens to implement the same statically-typed interface, but it's not likely to happen in proper test-cases because you're generally not going to pass any class-under-test the real-world implementations of its dependencies.
Most importantly, SOLID principles were made to encourage you to write cleaner, more understandable and maintainable code, and so was TDD. So if you do TDD properly, and you pay attention to how your code and your tests look (and it's not so hard because you get immediate feedback, API and correctness wise), you can worry less about SOLID principles, in general.
Are there cases in OOP where some or all of the SOLID principles do not lend themselves to clean code?
In general, no. History has shown that the SOLID principles all largely contribute to increased decoupling, which in turn has been shown to increase flexibility in code and thus your ability to be accommodating of change as well as making the code easier to reason about, test, reuse... in short, make your code cleaner.
Now, there can be cases where the SOLID principles collide with DRY (don't repeat yourself), KISS (keep it simple stupid) or other principles of good OO design. And of course, they can collide with the reality of requirements, the limitations of humans, the limitations of our programming languages, other obstacles.
In short, SOLID principles will always lend themselves to clean code, but in some scenarios they'll lend themselves less than conflicting alternatives. They're always good, but sometimes other things are more good.
Best Answer
The only principle which is not respected here is the Interface Segregation Principle:
E-book
inheritsstock
andreplenishStock()
which are absolutely useless for an electronic book.If you want to have a proper segregation of interfaces, you'd need something like the following, where Book describes a content, and PaperBook and e-Book correspond to sellable items with the generic content:
In your diagram, there is nothing wrong with the Liskov Substitution principle, since an
e-Book
is a specialisation of aBook
, and therefore has the full interface ofBook
(it's implicit). Of course,stock
andreplenishStock()
don't make a lot of sense, but nothing tells us that the contract is not respected (e.g. the replenishStock could very well set the stock to an arbitrary high value and meet all the preconditions, postconditions and invariants).Dependency inversion is not relevant here.