I have been reading a lot on design patterns lately and some of them can make our lives much easier and some of them seem to just complicate things (at least to me they do). I am curious to know what design patterns everyone sees as underunsed or underappreciated. Some patterns are simple and many people do not even realize they are using a pattern (decorator probably being the most used, without realized). My goal from this is to give us pattern-newbies some appreciation for some of the more complex or unknown patterns and why we should use them.
Most important "patterns" for a newbie is to
- keep it simple.
- Learn why supposedly good practices are good, so you can tell if they are good for the particular situation.
- You have to balance competing concerns for a situation, not just think about one. (decoupling v. cohesion, testing soundness v. completeness, good practices v. time)
As far as formal patterns with cool names go, I think reviewing patterns that focus on interfaces are helpful, because help get you thinking in terms of dependency and contracts, rather than just objects. Or as Uncle Bob (Martin) says, Depend on abstractions, not on concretions. So my answer is:
- Dependency Injection
- (to a lesser extent facade, decorator, adapter)
See Dependency Inversion Principal, one of the SOLID Principals, (which I'm sure you've already analyzed). Looked at narrowly, DI is very simple though it's often made over-complicated. I believe it's important to also look at it in a wider context, a way of thinking about every level and part of your design, not just code arranged in a certain way.
A small word of warning. I think some pattern-newbies become over-zealous with patterns and don't think code is good unless it's implementing a pattern, and that a pattern makes a solution good. They sometimes focus on patterns to the detriment of other good OOP/D practices. This is a mistake. Mr. Solid himself explains this here.
Common sense and efficiency can replace all design patterns.
EDIT: Seeing that commenters didn't get my point...
What I was trying to say is that instead of memorizing all design patterns and thinking in these terms, you can simply reflect about the problem for a while and solve it in an efficient and elegant way, based in common sense and your experience. With good probability, what you will come up with will be one or more of those design patterns. But you don't need to know them in advance in order to use them. Anyway, I could never master memorizing the names of those 20+ patterns and holding mental associations of what each name was standing for.
Balance
I mean, there are all sorts of patterns and guidelines, but all can be overapplied, resulting in an anti-pattern effect.
Experience about what and how to apply known practices comes with each line you write, each discussion you have with fellow coders and each reading write-ups from people who think different.
I'm a big fan of monte carlo unit testing. A lot of very complex, efficient algorithms are extremely hard to test because there are so many different code paths and edge conditions. If there exists a naive algorithm that does the same thing as your complex algorithm, or even one that simply makes different tradeoffs (like a hash table vs. a balanced tree), you can just generate tons of random input, feed it to both the naive and efficient algorithm, and make sure the results match. I've found tons of weird edge case bugs this way that would otherwise have been nearly impossible to find.
I implemented the other day the Strategy Pattern, it is used to encapsulate methods than you can swap at runtime. Sort of like the Command Pattern but more decoupled.
This is how I used it. I had to send files to third parties and I had a few options to choose from I had FTP, SFTP, FTPS and SMB.
Interface:
interface ITransferStrategry
{
void TransferFiles(System.Collections.Generic.IList<string> files);
}
Implementation:
public class FTPTransfer : ITransferStrategry
{
public void TransferFiles(IList<string> files)
{
// Transfer to FTP Code Here
}
}
Context:
public class TransferContext
{
private ITransferStrategry _strategy;
public TransferContext(ITransferStrategry strategy)
{
this._strategy = strategy;
}
public void Send(IList<string> files)
{
this._strategy.TransferFiles(files);
}
}
Client:
public void Client()
{
TransferContext context = new TransferContext(new FTPTransfer());
context.Send(files);
}
Its pretty easy and easy to maintain. Hope this helps anyone.