[Note: This is a reference book; I didn't actually read it cover to cover. I did read the first two chapters in full though.]
For the last couple years, I've been working as a software engineer, and I've found myself getting very confused by a lot of our Java code. Why do we use dependency injection What's a factory and why do we need them It felt like everyone else on my team had gotten some memo that I didn't receive.
It turns out that everyone else on my team *did* get some memo that I didn't receive, and that memo was this book.
This book describes the concept of design patterns, which are broad overarching ways of structuring your code. Design patterns are not about ensuring syntactic or algorithmic correctness; rather, they're about writing your code in a way that makes it easily extensible, restructurable, and maintainable. A design pattern is not something like "use for-loops for iteration"; that's just a specification of the syntactic structure of the language. And, as far as I understand, "test your code using unit tests" is not a design pattern (since it's not really about code structure), just a good testing practice.
An example of a design pattern is something like using a factory to make new instances of an object. Suppose you have some interface Foo, and an implementation of it called FooImpl. And suppose you frequently need to create new instances of this. You could pepper your code with "Foo foo = new FooImpl()", but then suppose you wrote a new, better implementation of the Foo interface called FooImplImproved or something. You would have to find each instance of "new FooImpl()" and replace it with "new FooImplImproved()". This is a pain. So instead, you could create an object called FooFactory which returns instances of Foo. Then you could do something like "Foo foo = FooFactory.create()". This create() method would then just contain "return new FooImpl()". Then, when you wanted to change FooImpl to FooImplImproved, you would only have to change it in one place: in the FooFactory.create() method.
I think I would have benefited from encountering this book in college, before I ever worked as a professional software engineer. But I wouldn't have fully understood or appreciated it then. I still don't fully understand it, but now that I've worked in industry, I have a much better understanding of the need for maintainable code.
This book definitely changed how I think of programming / software engineering. And, if I'm understanding correctly, back when this book came out, it changed how *everyone* thought about software engineering. As far as I know, this book introduced the concept of a design pattern. The book tells you about all sorts of different design patterns, but I think the main impact of the book was not teaching people individual design patterns, but introducing them to the concept of a design pattern altogether. Once you have that concept, you can identify and use new design patterns more more easily. The specific design patterns presented are certainly useful, but the concept itself is more important.
Also, in the spirit of "reading the history of philosophy backwards", this book taught me a lot about what was known about software engineering back in the early 90s, just based on what it felt like it needed to explain to its audience. One thing that surprised me was that it went out of its way to explain the separation into interface-based inheritance and implementation-based inheritance. I'd always taken that distinction for granted, because it's built into Java, which was my first language. But I realized, after reading this book, that the distinction between implementing an interface and inheriting an implementation was not always obvious. And in C++, that distinction is not a syntactic feature, but merely a design pattern. (Which makes me wonder what it would look like if OO languages incorporated more of these design patterns into their syntax.) Anyway, "program to the interface, not the implementation" was something I had only learned since starting at this job; it's not something that was covered during my undergrad education. So I was glad this book went out of its way to emphasize its importance.
Anyway, this book absolutely gets 5 stars, for introducing me (and also the rest of the software engineering world) to a new conceptual framework that allows software engineers to do a much better job. I expect that I will return to this book very often and will always view it as a useful resource.