Object-oriented programming has been around for a long time, if we restrict looking at languages that support it for about 50 years. When I got started with it personally, the concept clicked. It appealed because of the way it maps to the real world. To be able to write code that behaves in the digital world the same way its counterpart does in the physical world.
And then design patterns happened.
Originally, design patterns arose as answers to specific problems. A list of solutions that had proved themselves, written down for convenience so you could look them up when you needed to. And there’s nothing wrong with that.
What now happens is an inherent symptom of the job market, job interviews and contract-based work. When interviewing one can go into the ability to solve problems, or filter on concrete lists of knowledge. For developers the list of design patterns is a popular go-to to easily fill a session of questions. This gives an inflated sense of importance on these patterns rather than on the problems they solve, especially when one has only little experience. Combine this with one of the great human imperfections – the desire to show off – and we have a recipe for design pattern factories (pun intended).
This has inevitably led us to the abhorrent architectures we see this day: those that link together design patterns until the software works – If you have a SomethingStrategyBuilderFactoryDecorator you might have gone too far, real example. Layers and layers of abstractions that have no significance, other than that it fits ‘the design’. No problems are solved, only lines of code linked. Unit testing is done to check whether that line remains unchanged rather than if it still does what it’s supposed to. This seems to be most prevalent in N-tier architectures, though other alternatives suffer this same caveat because of importance placed on knowing patterns.
At some moment I had high hopes for Domain-Driven Design. The term itself felt like the return of OOP in its true meaning. Except the design pattern guerillas had already had their way with that concept, resulting in this iconic quote from the DDD Wikipedia page on the disadvantages of DDD: “In order to help maintain the model as a pure and helpful language construct, the team must typically implement a great deal of isolation and encapsulation within the domain model.”. In other words: templated design patterns without a cause.
What do I suggest?
Ok, to get this out of the way, when I do interviews with new candidates, I don’t ask about design patterns. I’ll ask if they know them and might discuss the importance of being able to apply them when necessary, but it’s not a part of the interview.
I will ask about SOLID though. Not what the letters stand for, but what it implies. Single responsibility does not mean the code is only referenced once, it means that it does one thing and does that well and correct. Liskov Substitution means you should think about the hierarchy of your objects, and their common denominators carefully, so you don’t end up typecasting as quick fix once it’s too late. Interface segregation does not mean every class should have an interface, but that an interface should be a meaningful abstraction rather than a list of what’s in a class.
Second, I’m a fan of hexagonal architectures and (micro)services. The outer edges of hexagonal architectures are mostly code that can be (auto)generated, from service interfaces and data access to services. They don’t need to much attention generally once you’re writing functional code. The application is then reduced to writing code that can work independent of the environment it’s in and implements the domain at hand. It’s easily portable, isolated, unit testable and one can focus on writing the best code for that situation.
And DRY doesn’t mean putting an ID field in a base class so it’s not copied, but if you’re duplicating functionality than put it in a separate function or class. These rules are on functionality more than on the duplication of some string of characters somewhere.
Third, there are patterns I don’t use, at least not in the way they are used in most situations out there. These are some examples:
- Singleton – for obvious reasons, won’t dive into it.
- Factory – a popular topic of debate, these are out there a lot. However, they are often used to create functions for execution rather than domain models, something any self-respecting DI container can easily do for you.
- Repository – not one of the official ones but very popular. Note that it was introduced to abstract away the code for data access, so one could deal with that in an isolated way. This will also have the benefit of having a set location for checking SQL injection and other vulnerabilities. These days there are very nice solutions that do exactly this for you, in .Net the most popular one is Entity Framework.
Note that an insane amount of Repository use in the field don’t abstract anything, they pass along data, might obfuscate code and that’s it (anyone with an IXxxRepository that has 30+ functions in it?).
If we all INVEST our time into this, Build DRY, SMART software to KISS, we’ll have some SOLID solutions in no time.