All designs should be as simple as possible. You should start with KISS, YAGNI, and Do The Simplest Thing That Could Possibly Work principles. Complexity and patterns should only be introduced when they are needed for practical extensibility.
This needs to be at the top, in the introduction. Especially in Java and C#, I've seen far too much "over-patterned" code, in contrast to the opposite.
I think this has a lot to do with how OOP is taught. At uni, on the same semester we were introduced to C++ with classes, OOP, UML, and design patterns. This of course creates in the mind an association between all of these things -- a feel of 'if I'm doing OOP then I should also use these patterns'.
The thing is, during the class we understood what the patterns were. However, since the actual programming project was comparatively small we didn't actually understand what a need for following these patters looks like. We didn't see a case where the simple way just didn't cut it, we were simply told to use them because they are good. The effect of this is that, unless someone with experience then comes and mentors you, you will overengineer all of your solutions because you don't know any better. We were taught the patterns but didn't understand the reasons. And I suspect many other engineers have had similar experiences
I wrote a bit of C recently, it's impressive how patternless it makes you think. At least not in the OOP way. You may think in patterns, but it's not encoded in source. It's implicit structure. It's also very different because every bit of help you allow in your source is carefully thought out, you want it to fit the fragile land of C and also now you're moving bytes somehow explicitely, you tend to think more about memory and cpu perf.. again less pattern envy.
ps: I also forgot to say, even though I'm a fp head these days, imperative C felt fun, that is if you carefully stay into state -> state mindset ;)
Unfortunately this means you wrote several copies of essentially the same code but with minor differences, and if you find and fix a bug in one the others are still wrong.
You need to learn them to be able to read the code that uses them - whether it is used the right way or not. E.g. if you encounter the word 'adapter' or 'decorator', it is easier if you know what it means (and that you should not use those words for things that are not patterns).
I'd argue that Design Patterns mostly appeared because of Java. Because of people hitting the wall with Java; Java does not allow metaprogramming; Java did not allow class members (it allows instance members); Java does not allow multiple inheritance; Java has a cumbersome and underpowered exception handling system; Java's OOP is really strict and limited; no real functions that can be passed around (not even in Java 8); recursion can't use tail call optimization... etc etc
because of ALL this, then developers had to resort to applying the same workarounds. And such workarounds were collected, tagged, and proposed as
Design Patterns
by the gang of four.
But seriously, if the authors write,
"All designs should be as simple as possible. You should start with KISS, YAGNI (...)"
Then they should be looking at alternatives to Java in the first place. It's 2017 and there are other alternatives that even run in the JVM (if this is a requirement).
Java has always allowed for class members using the "static" keyword.
The GoF book predates the public release of Java. The code samples in GoF are in C++ and Smalltalk.
Correct me if I'm wrong, but Java 8 does allow functions to be passed around. E.g., if a function takes as input a Runnable, I can pass a no arg lambda expression as input.
>I'd argue that Design Patterns mostly appeared because of Java. [...] And such workarounds were collected, tagged, and proposed as Design Patterns
Fyi... "Design Patterns" in 1994 predates Java 1995.
Design patterns in some form have been around as long as computers have been around. Independent teams of programmers notice a repetition of code or a higher-level idea and they make it a convention. E.g. the "observer" pattern was something Smalltalk and Objective-C programmers did more than a decade before Java. Before "Design Patterns" became a meme/book, different teams used different vocabulary. Maybe they'd call it the "event watcher code convention" instead of "observer pattern". DP was a book of past practices gathered from the real world and it got everyone to converge on the same vocabulary to label what they were already doing.
The Gang of Four book was written in 1994, and Java was released to the public in 1995. The patterns were actually developed by C++ programmers, and the authors sought to categorize and formalize them. The patterns are useful in the context of object-oriented programming in general, not just Java.
The language most of the examples are written in, C++, does allow multiple inheritance and metaprogramming, and a bunch of other stuff Java does not allow. The patterns are still useful in C++ despite those features, which is why C++ developers came up with them in the first place.
I realize i made an anachronical mistake. However the Design Patterns book and the 'design patterns' mindset is most popular with Java developers.
Sorry but I do stand on my criticism of Design Patterns as applied today.
> The language most of the examples are written in, C++, does allow multiple inheritance and metaprogramming,
You're being very generous with C++ if you consider it does allow metaprogramming. It does not, if we compare it with the languages that make good use of metaprogramming: Lisp, Scheme, Racket, and now Julia.
There is a ton of usability, scope, and power difference between the metaprogramming of C++ templates versus the metaprogramming of the Lisp family of languages (and Julia), which is what is usually considered as metaprogramming in the practical sense, languages that are arguably designed around metaprogramming as a fundamental feature.
Just as assembly language is also turing complete, but there is also a ton of usability difference between it versus C/C++, in favor of C/C++ and with little performance penalty.
C++ template meta programming isn't Turing complete in a way that is useful.
Turing Complete means that you can calculate any partial recursive function (given some suitable input encoding).
For instance, in pure Lambda Calculus that has only functional terms (no numbers), we an make integers out of function using "Church numerals" and then calculate functions over these numbers. No amount of such calculation will produce an integer object, though.
Similarly C++ template cannot calculate any C++ syntax whatsoever, in spite of Turing Completeness. You get template functions and template classes; that's it.
Tail call optimization is a way for compiler to align functional recursive algorithm with stack-based procedural reality. Since Java is not a functional language and has sufficient expressive power to write iterations as... iterations, good programmer will just write a for loop and will not bother about a feature that is basically unnecessary in multi-paradigm language. Oh, by the way, recursion is a functional design pattern, isn't it?
> Since Java is not a functional language and has sufficient expressive power to write iterations as... iterations, good programmer will just write a for loop and will not bother about a feature that is basically unnecessary in multi-paradigm language.
Ok, so you think recursion is unnecessary in a multi-paradigm language.
Which implies also that multi-paradigm languages shouldn't be really multi-paradigm languages... Java, by the way, is hardly a multi-paradigm language.
And you imply that the reason one uses recursion is because one can't "write iterations as ... iterations".
You should realize that recursion is used whenever expressing the problem in a recursive way would make the problem easier to program and/or clearer to express, etc. Not because of recursion being fancy or fashionable.
Recursion is a computer science technique, not a "Design Pattern".
And here is a second source if you don't believe me or iluwatar
>In functional programming, a monad is a design pattern that defines how functions, actions, inputs, and outputs can be used >together to build generic types,[1] with the following organization:
>
> Define a data type, and how values of that data type are combined.
> Create functions that use the data type, and compose them together into actions, following the rules defined in the first >step.
I would also mention "Emergent Design" along with KISS and YAGNI. Do not start your code base with a collection of design patterns. This is the usual cause of a complete over designed mess. Instead as the code grows look for the patterns to emerge and then apply them.
My take: you do not truly understand modularity until you have had to work with a third party framework where your software is integrated as plugin. Working with Eclipse / PDE / CDT has taught me more about modularity (good and bad) in 2 years than a decade of green field development (plus, I can now write a usable Eclipse plugin faster that I can write the equivalent Vim function ...). I also recommend "Practical API Design" (which contains both large scale advise and tiny but important details regarding source compatibility vs binary compatibility with aspects that have been unknown to me after several years of developing Java) and "Java Application Architecture" (which contains decoupling strategies, but you only learn to appreciate those after you have encountered the generic problem pattern).
28 comments
[ 3.5 ms ] story [ 51.9 ms ] threadThis needs to be at the top, in the introduction. Especially in Java and C#, I've seen far too much "over-patterned" code, in contrast to the opposite.
The thing is, during the class we understood what the patterns were. However, since the actual programming project was comparatively small we didn't actually understand what a need for following these patters looks like. We didn't see a case where the simple way just didn't cut it, we were simply told to use them because they are good. The effect of this is that, unless someone with experience then comes and mentors you, you will overengineer all of your solutions because you don't know any better. We were taught the patterns but didn't understand the reasons. And I suspect many other engineers have had similar experiences
ps: I also forgot to say, even though I'm a fp head these days, imperative C felt fun, that is if you carefully stay into state -> state mindset ;)
I'd argue that Design Patterns mostly appeared because of Java. Because of people hitting the wall with Java; Java does not allow metaprogramming; Java did not allow class members (it allows instance members); Java does not allow multiple inheritance; Java has a cumbersome and underpowered exception handling system; Java's OOP is really strict and limited; no real functions that can be passed around (not even in Java 8); recursion can't use tail call optimization... etc etc
because of ALL this, then developers had to resort to applying the same workarounds. And such workarounds were collected, tagged, and proposed as
by the gang of four.But seriously, if the authors write,
"All designs should be as simple as possible. You should start with KISS, YAGNI (...)"
Then they should be looking at alternatives to Java in the first place. It's 2017 and there are other alternatives that even run in the JVM (if this is a requirement).
The GoF book predates the public release of Java. The code samples in GoF are in C++ and Smalltalk.
Correct me if I'm wrong, but Java 8 does allow functions to be passed around. E.g., if a function takes as input a Runnable, I can pass a no arg lambda expression as input.
https://www.tutorialspoint.com/java8/java8_functional_interf...
Beyond that, by at least 1.4 you could pass methods using the reflections API, though that was kludgy in a lot of ways.
Fyi... "Design Patterns" in 1994 predates Java 1995.
Design patterns in some form have been around as long as computers have been around. Independent teams of programmers notice a repetition of code or a higher-level idea and they make it a convention. E.g. the "observer" pattern was something Smalltalk and Objective-C programmers did more than a decade before Java. Before "Design Patterns" became a meme/book, different teams used different vocabulary. Maybe they'd call it the "event watcher code convention" instead of "observer pattern". DP was a book of past practices gathered from the real world and it got everyone to converge on the same vocabulary to label what they were already doing.
The language most of the examples are written in, C++, does allow multiple inheritance and metaprogramming, and a bunch of other stuff Java does not allow. The patterns are still useful in C++ despite those features, which is why C++ developers came up with them in the first place.
Sorry but I do stand on my criticism of Design Patterns as applied today.
> The language most of the examples are written in, C++, does allow multiple inheritance and metaprogramming,
You're being very generous with C++ if you consider it does allow metaprogramming. It does not, if we compare it with the languages that make good use of metaprogramming: Lisp, Scheme, Racket, and now Julia.
Just as assembly language is also turing complete, but there is also a ton of usability difference between it versus C/C++, in favor of C/C++ and with little performance penalty.
A language can be Turing Complete, yet do only pure calculation plus I/O strictly in Roman numerals on two predefined streams.
Good luck decoding a JPEG with that, right?
Turing Complete means that you can calculate any partial recursive function (given some suitable input encoding).
For instance, in pure Lambda Calculus that has only functional terms (no numbers), we an make integers out of function using "Church numerals" and then calculate functions over these numbers. No amount of such calculation will produce an integer object, though.
Similarly C++ template cannot calculate any C++ syntax whatsoever, in spite of Turing Completeness. You get template functions and template classes; that's it.
Ok, so you think recursion is unnecessary in a multi-paradigm language.
Which implies also that multi-paradigm languages shouldn't be really multi-paradigm languages... Java, by the way, is hardly a multi-paradigm language.
And you imply that the reason one uses recursion is because one can't "write iterations as ... iterations".
You should realize that recursion is used whenever expressing the problem in a recursive way would make the problem easier to program and/or clearer to express, etc. Not because of recursion being fancy or fashionable.
Recursion is a computer science technique, not a "Design Pattern".
https://github.com/iluwatar/java-design-patterns/tree/master...
And here is a second source if you don't believe me or iluwatar
>In functional programming, a monad is a design pattern that defines how functions, actions, inputs, and outputs can be used >together to build generic types,[1] with the following organization: > > Define a data type, and how values of that data type are combined. > Create functions that use the data type, and compose them together into actions, following the rules defined in the first >step.
https://en.wikipedia.org/wiki/Monad_(functional_programming)