SOLID Wash Tunnel is an educational programming series on how to model an automatic wash tunnel with SOLID principles & various design patterns. The programming language used throughout the project is C#.
The motivation for this project came after being asked by a friend the question:
I can understand Design Patterns and SOLID Principles one-by-one, but fail to understand how to apply multiple of them in a project?
There are countless examples of SOLID & Design patterns around, but most of them use completely different examples for different patterns. I have failed to find an example or project where the author implemented all (or most) of the patterns.
Nothing is better for a junior/mid level developer than seeing a fully fledged example of the design patterns and their interaction to build a complete project.
SOLID Wash Tunnel tries to do exactly that!
During this series we will go over:
- How to create a simple Inversion of Control container.
- How to implement various design patterns and see their interactions.
- Where we adhere to the SOLID principles and where not, and the reasoning behind it.
The domain as seen from the title is an automatic vehicle wash tunnel. Customers can send their vehicle to be wash by the tunnel. They use the UserPanel to configure various things like:
- Select any of the built-in wash programs, or create a custom program.
- Select whether they are an individual or company customer (They get discounts if they represent a company).
- Receive an invoice at the end in the preferred currency.
Table of contents
- Introduction to SOLID wash tunnel
- Implementing a custom IoC container
- Dependency Injection Technique
- Simple Factory Pattern
- Singleton Pattern
- Command Message Pattern
- Mediator Pattern
- Service Locator Anti-Pattern
- Fluent Builder Pattern
- Chain of Responsibility Pattern
- Decorator Pattern
- State Pattern
- Visitor Pattern
- Adapter Pattern
- Observer Pattern
- Proxy Pattern
- Demo of the wash tunnel
The SOLID Principles
Before we continue on with the rest of the articles, lets define each of the SOLID principles, and try to give a "plain english" version of them.
Single Responsibility Principle
"There should never be more than one reason for a class to change." — Robert Martin.
A class should concentrate on doing one thing.
The SRP states that a class should focus on doing one thing, or have one and only one reason to change. This does not mean it should only have one method, but instead all the methods should relate to a single purpose (i.e. should be cohesive).
Open Closed Principle
"Software entities (modules, classes, functions, etc.) should be open for extension, but closed for modification." — Robert Martin.
Alter class behaviour using inheritance and/or composition.
Following the OCP should make behaviour easier to change, and also help us avoid breaking existing behaviour while making changes. The OCP also gets us to think about the likely areas of change in a class, which helps us choose the right abstractions required for our design.
Liskov Substitution Principle
"Functions that use pointers or references to base classes must be able to use objects of derived classes without knowing it." — Robert Martin.
Derived classes should behave nicely when used in place of their base class.
The LSP ensures that inheritance is used correctly. If an override method does nothing or just throws an exception, then you're probably violating the LSP.
Interface Segregation Principle
"Clients should not be forced to depend upon interfaces that they do not use." — Robert Martin.
Keep interfaces small and cohesive.
The ISP is about keeping interfaces (and abstract classes) small and limited only to a very specific need. If you have a fat interface, then you are imposing a huge implementation burden on anyone that uses said contract.
Dependency Inversion Principle
"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."
— Robert Martin.
Depend on abstractions, not on concrete implementations.
The DIP says that if a class has dependencies on other classes, it should rely on abstractions rather than their concrete types. The idea is that we isolate our class behind a boundary formed by the abstractions it depends upon. If all the details behind those abstractions change then our class is still safe. This helps keep coupling low and makes our design easier to change.
The other side of the DIP relates to dependencies between high and low level modules in layered applications. For example, a class accessing the database should not depend on a UI form used to display that data. Instead the UI should rely on an abstraction over the database access class.
DIP becomes really useful when paried with an IoC container & Dependency Injection.
DIP is about shape, IoC is about direction, and DI is about wiring.
There are some very important points I want, you the reader, to take from this series:
- Principles and patterns are there to make your codebase cleaner, easier to maintain and speed up the development process.
- Use them where it makes sense to, do not use them just for the sake of it.
- Do not try to use every single one of them, take what you need and apply it where it helps.
- Sometimes you will notice that adhering to one of them makes adhering to the other harder. They can be forces that point in different directions. This is more apperant in design principles rather than the patterns.
- Restrict yourself to apply them in very trivial use cases.
- Use them, but do not abuse them!!!
Start the series on IoC container.
If you found this article helpful please give it a share in your favorite forums 😉.
The solution project is available at GitHub.