SOLID - bradyclifford/sdlc GitHub Wiki
From Uncle Bob:
SOLID Principles
S: Single Responsibility.
This applies to the smallest method up to a bounded context. There should only be one reason for a context to change. Should have a single responsibility. Now you don't wan to swing that pendulum to far. Responsibility doesn't mean it does one thing. But rather, it has one responsibility.
O: Open Closed.
Closed for modification, open for extension. I see it as you can extend a classes behavior, without modifying it. Dependency injection helps with this principle.
L: Liskov Substitution.
Can never say this name. Derived classes must be substitutable for their base classes. I usually keep safe from this by favoring composition over inheritance.
If it looks like a duck, quacks like a duck, but needs batteries, you probably have the wrong abstraction
I: Interface Segregation.
Keep interfaces small. The single responsibility spills into this principal. A client shouldn't have to implement something it doesn't need or use.
D: Dependency Inversion.
Depend on abstractions, not on concretions/implementations. This principle favors composition over inheritance.
- High level modules should not depend upon low level modules. Both should depend upon abstractions.
- Abstractions should not depend upon details. Details should depend upon abstractions.
You wouldn't wire your lamp directly to your wire socket
A rule of them for me: all dependencies a class needs to operate correctly, should be passed in during object creation.
Thoughts
What is interesting is each principle builds on each other. You easily can depend on abstractions when you utilize interfaces that are small, that serve a single responsibility. You adhere to the open/close principle by injecting your abstractions into your implementation.
Another similar acronym
Don't be STUPID:
- Singleton (class that has multiple uses)
- Tightly Coupled
- Untestable
- Premature Optimization
- Indiscriminate Naming
- Duplication
Questions
When to use the pattern? When not to?
In most cases, I feel this pattern / policy / principle should be followed always. However, like any good principle, you don't want to swing the pendulum too far either way.
It should be used as a tool, not the goal.
- Make it as simple as possible
- Pain and friction will indicate when your design needs to grow
- Use SOLID (and other) principles as guidelines while refactoring
How to migrate legacy systems to and from this pattern?
For this pattern, which many others, it requires good automated tests to be able to refractor. But in many cases, legacy systems are too coupled.
One solution that could be used (and I forgot the name, but I believe branching is part of it) is a from my research into Domain Driven Design. You standup something similar to an ACL but within the legacy code base. You start to refactor small sections of code using the ACL as an interface / interop into the legacy mess.
What are the tradeoffs of using the pattern (strengths and weaknesses)?
Strengths: simpler code, easier to read, easier to maintain Weaknesses: more files to manage perhaps, but I don't see that as a weakness
Where have you seen this pattern used/abused in our systems?
This is used in most of our systems and is a well adopted pattern within our industry.
What are the maintenance concerns with utilizing this pattern, both during development and during operation in production?
None, I feel it actually reduces maintenance concerns long term.
What problem do you think Liskov Substitution is trying to solve?
Liskov Substitution focuses on the "is a" concept. But even more than that, I feel this principle helps prevent a code smell I have heard called "utility inheritance".
Many times you see developers introducing a base class purely because two or more entities have some members in common; they don't want to duplicate that code; such is not being DRY. OOP is meant to model behaviors, not data. Classes violate encapsulation even before they violate LSP.
Bringing Domain Driven Design and the DRY principle into the conversation. Two entities having the same functionality (code) doesn’t mean they violate DRY. Both can hold their own semantics. It just happened that they do it using the identical code in such a particular case.
DRY should only restrict the presence of domain knowledge. Entities should be able to be developed separately from each other. Both represent different parts of the domain.
One of the properties of a domain object is that once created it can never enter into a corruptive state. All the examples on the web and books that talk about LSP use the simple example of a square and rectangle class. The square inherits the rectangle, but it violates LSP. You can argue the philosophical reasons why, that the "is a" concept is violated, however, the easiest way I have been able to follow this principle is to think of it in a DDD mindset. With a rectangle containing a public interface that allows you to change the width and height when used as a square, its state can become corrupt when the width is changed and not the height. The model should always be valid and should not be able to be in a corruptive state. We have a big problem with that in much of our code bases.
You could also argue that this relates to interface segragation. A code smell I continue to see is the generation of header interfaces. The interfaces mimic the implementation of the class. When it should be exposing a subset behavior of the classes public Api. Every class has its own interface that is defined as a set of public methods already.
To summarize:
DRY only restricts the presence of domain knowledge Classes should only expose public api that should never allow them to be in a corruptive state for the behavior they represent Why do we want to balance inheritance with composition? Relationships are not always a "is a", so instead, use composition over inheritance. Don't try to take DRY too far.