I'm going to wind up farther afield than most of the other answers. But let me start with the obvious place. We all think we know the MVC pattern. But every framework uses it in different ways. And despite the fact that we all know it came from Smalltalk, very few people know that what we call MVC today looks very little like Smalltalk's version. (Old Smalltalk programmers have a common complaint that everyone thinks they have learned from Smalltalk, but everyone has missed the point.) Read http://st-www.cs.illinois.edu/users/smarch/st-docs/mvc.html for an overview of the original version. The next time that you're doing a complex AJAX application, stop and think. Perhaps a design with interacting components, each with their own model, view and controller, might make sense.
Moving to a different field, map-reduce is worth knowing about. This is a pattern that is used in distributed computing. The idea is very simple. Organize your thinking into stages that look like this:
- Have a mapper that takes a stream of facts, and emits a stream of key/value pairs. The emitted stream may be larger or smaller than the original facts.
- Magic happens. (I'll explain the magic in a second.)
- Have a reducer that gets a key followed by all values associated with that key, and does something. (It can, if it wishes, emit the data for use in another map-reduce.)
The trick is that all of this can be done in a completely distributed fashion. This technique was invented and popularized by Google, which uses it for such tasks as analyzing the whole web.
How does it work? Well generally you use a framework like Hadoop that takes care of the details. You write the map and the reduce, tell it how many mappers and reducers you want, and tell it where the work is to be done. It takes care of dividing up work for the mappers, uses a distributed sort in step 2 (usually this is a mergesort), then sends that data to the reducers who do what they want with it. Since all of these steps are distributed, you can scale across a large number of machines.
Everyone always uses text analysis for the example, so I won't. Instead I'll discuss generating all of the primes up to a trillion. You want to divide the work across many machines. First we generate a small amount of input to get started. One is the list of all primes under 1 million. The other is a list of pairs of ranges. The second might start with
(2, 1000000), (1000001, 2000000), (2000001, 3000000), ... If a mapper gets a single number, it would emit key/value pairs with the keys being all of the multiples of that number starting at its square, and going up to 1 trillion, and the value just being the number. If the mapper gets a range it emits key/value pairs where the keys and the values are all the numbers in that range. The reducer is simpler, if it gets a key with only one value, that is a prime which is stored somewhere. (For instance you could use a distributed key/value store like Redis or Cassandra.) If finds multiple values, that's a composite number so do nothing.
This code is easy to write, and will run on anywhere from 1 to 10,000 machines, scaling pretty much linearly with the number of machines available to it. With practice the basic technique is adaptable to a surprisingly large variety of problems.
Moving on, a common design idea in functional programming is the idea of having a hash that maps keys to functions that then can do something. Many of the functions will have been generated as closures. The point of this may not be obvious to an OO programmer, because you can do this with OO as well. But the problem is that an OO approach to the same strategy gets painfully verbose, so that you can no longer make out the forest for the trees. See http://www.perlmonks.org/index.pl?node_id=34786 for an example of the technique in a situation that is realistically complicated. (The description there of what functional programming is, is somewhat wrong. But the technique demonstrated is widely used.)
Of course the fundamental problem for an OO programmer learning functional techniques is that you decompose problems in very different ways in the different styles. I like to claim that objects make good nouns, while functions make good verbs. If you have experienced both, that concisely sums up the difference. But if you've never done functional programming, it is hard to see how one would organize a program using verbs instead of nouns. Therefore I'm going to strongly recommend working your way through The Structure and Interpretation of Computer Programs, available online at http://mitpress.mit.edu/sicp/full-text/book/book.html.
And finally, there is the technique of writing complex programs by writing a DSL that simplifies writing the program you want to write. There is a lot of verbiage out there on DSLs, particularly from Ruby programmers, most of which is pretty bad. So I'm going to recommend an old Lisp book instead. On Lisp is a true classic by Paul Graham. It is available online at http://www.paulgraham.com/onlisptext.html. While on the surface it is a book about Lisp, if you scratch under that surface it is a book about how you could program if you had a set of tools that few languages have. If you scratch under that surface, it will let you dream of better approaches, and it is not that hard to build the tools that you need. There is an old joke that Any sufficiently complicated C or Fortran program contains an ad hoc, informally-specified, bug-ridden, slow implementation of half of Common Lisp. After reading On Lisp you will understand why this is a useful thing to do, and know enough to be able to usefully do it yourself.