I don't necessarily disagree with what is being said, the patterns themselves are perfectly reasonable, and maybe his style works better on very complex programs, that I don't write, but it seems like his is adding unnecessary classes and complexity in the name of extensive "decoupling".
A python talk I liked titled: Stop Writing Classes, is seemingly antithetical to this book's advice, which is rife with classes. Particularly, if you have a class with two methods, one being init, you probably don't have a class. Many of the classes in this book fit the bill.
After re-watching Stop Writing Classes, that I agree very much with, presented by a python core developer, I think this author seems to be trying to force python into Java-like object oriented box. The book does, in fact, mention Java-like interfaces several times.
Personally I prefer more functional programming to OOP, but that doesn't make one better or worse. And, while I wasn't a huge fan of this book, it was interesting, a different perspective than my normal one, and, if nothing else, it was a breeze to read.
My biggest pet peeve comes toward the end of the book when the author talks about constantly upgrading you code. Adding new features. Maybe I am getting to be a curmudgeon, but I prefer FEWER updates (security aside).
You should build your programs in such a way that you can constantly swap out bits of the program without changing any other part of the code. Every period—depending on the size and scope of your project, this might mean a year or five years or six months -you want to have cycled out all the old code and replaced it with better, more elegant, and more efficient code.
Let me repeat that last line:
...five years or six months -you want to have cycled out all the old code and replaced it with better, more elegant, and more efficient code.
While I don't agree with much of the book, this part here is the worst. Constantly updating your code is the bane of software, not the holy grail. It will lead to bloat and bugs. Code should be updated as little as possible. Write it right, IMHO.
How often is grep updated? sed? awk? And they are great tools, timeless. How about Firefox? Every few weeks? And it is a bloated POS. Good code should move towards being static, despite what the buzz is about dynamic, constantly changing. As far as I am concerned, constantly updated code exists primarily to keep developers fed. Imagine any other industry providing "updates". Oh, I have to bring my car in AGAIN? I need a patch on my refrigerator or my food will spoil!? It wouldn't fly. Yet in the tech world, shoddy, not-yet-ready code is lauded as dynamic and agile.
Chapter one starts typically, with the obligatory setting up your environment. This seems ridiculous to me, given the level you'd be at if you are learning patterns.
It offers comparisons and some critique of the classic Gang of Four book, which introduced, or at least mainstreamed, the concept of patterns into the programming world.
There is some great advice on learning: go slowly and deliberately, talk to others who are more skilled than you, and code, code, code. Write something, throw it out and do it again if you have to. This will also help you learn that it is sometimes OK to chuck out ten thousand lines if you now see how you can solve the problem in one hundred lines. Don't think of the ten thousand as wasted, you needed to write them to get to the one hundred.
From here, each chapter goes on to focus on a particular pattern. It is usually introduced with suboptimal code and then, through a series of revisions, if ends up taking the final shape of the pattern in question.
The first pattern covered is the singleton. While there are many uses that this is perfectly fine using, like logging or load balancing, it is really a fancy way to have a global variable. As with all of the patterns and advice in the book, none of them are catch-all; each is a unique tool that should be deployed only when appropriate.
Next is the prototype pattern. The example give is of a real-time strategy game where you need to construct units. It seems to create new units by copying old units, but it is unclear how the first unit is created. It does clearly separate the values, statistics, and abilities, of units into data read from disk, and is not included in the code itself. This seems obvious to me, but what do I know?
The factory is the first one I am not really sold on. I'm missing the benefit over straight inheritance. A @staticmethod that takes a string defining what to construct, or a number of methods, one for each thing you want to construct. You'll need an if statement in the former and a number of methods in the latter. As I'll repeat again and again, I could be missing something with this.
With the builder, like the factory, I don't see the benefit. You still need a method for each part of the, in the example's case, form. Using several classes, creating objects that reference one another, seems messy. Maybe in a larger code base than the example or I am used to working with (only my little personal programs), but I'm not seeing it jump out at me why this is better than a function to decide what other function will be called, all in one class.
1 2 3 4 5 6 7 8 9 10 11 12
def create(self, field_list): for field in field_list: if field["type"] == "text_field": buildTextField(field) elif field["type"] == "whatever_field": buildWhateverField(field): else: buildBlahField(field) def buildTextField(field): # build text field return text_field
Again, I am probably missing something, but this is what I would do. I would need to add an elif and a method for building each new field type, but you'd have to do the same with the builder pattern.
The adapter pattern I completely agree with. If you have a library that provides something, but not exactly the way you'd like it, you can create an adapter to handle what you require. You can even pass everything else that you don't need to modify off to the base library to handle.
Here the open/closed principle is also discussed. Code should be open to extension, but closed to modification. You can use adapters (is this the same as wrapper?) to extend code without have access to, or touching, the source.
I never thought of decorators as a pattern, but it isn't unreasonable. The pattern of having to do something before or after a number of functions. There is really not much to say about these, but class decorators were something I wasn't aware of. It mentions Flask as a good source to read to see decorators used interestingly (the routing).
The facade pattern feels much like an interface, a single point, likely a class, that can be seen externally. All the messy business is hidden behind a clean implementation. The guts can be modified or swapped out and the facade should keep on working on the client side without modification.
This one seems reasonable, kind of like an adapter on steroids. I can see it being more valuable if you are building a library or something many other people, who want a simple, clean product.
With the proxy pattern, you want the interface to remain constant, with some actions taking place in the background. Conversely, the adapter pattern is targeted at changing the interface.
the example, hiding the memoization of a Fibonacci generating function, is another example showing how to hide the guts of a program behind simpler interface.
The chain of responsibility is on of the more confusing patterns, but upon sussing it out, I like it. It uses handlers to handle certain kinds or parts of a request.
You can use a dispatcher to create a chain of handlers (functions) and then iterate through them, each iteration will produce an updated request and pass it to the next handler. a catch all handler at the end cleans up the leftovers.
The command pattern tries to decouple commands from execution, creating essentially an execution queue, that gives you an undo stack for free. It feels like a producer-consumer. What I wonder is, why not use an actual queue?
The interpreter pattern hinges on domain specific language. This is one of my least favorite patterns, but is a neat solution if you have some very specific, domain, problem to solve.
The example was a restaurant that had complex rules for discounts. You can use the interpreter to allow the restaurateur to write, in a domain specific language, how the specials will be provided. This DSL is then interpreted by the python program.
Here is another place where I see the author as being class happy. Everything, even the day of week and time become (unnecessarily IMHO) objects.
In contrast, one of my favorite patterns is that of the iterator. At the end of this chapter it says something like: this will lead you into the realm of functional programming. Makes sense that I like this pattern a lot.
The observer pattern is another one I am all on board with. The author seems to be letting up on the classes, showing that the abstract base class isn't really needed with python's duck typing. He even says it forces a Java-like interface. Why would I want to force python to be Java-like?
I watched a few videos, to dig deeper, on ABC and python, and my takeaway was, as was one of the presenters, that they aren't really in the spirit of python, but they are there if you need that specific tool.
The decoupling and loose coupling concepts I am on board with, though I've heard a lot of people say that language simply doesn't come up as much as you'd think, given how drummed into modern CS students they are.
With the state machine, which in general I love, the author goes class crazy again, making every state its own class. Dictionaries would probably work, although it would be problem dependent.
The strategy pattern tries to avoid 'long' if-else blocks, something the author seems to despise. I wonder what he thinks about case statements?
You can use an executor function initiated with another function that implements the particular strategy you want, at run-time.
I also like this pattern. Though the chapter started out by writing several (unnecessary) classes, it made its way back to only 1 executor function and x strategy functions.
The template method; another pattern I like, when appropriate. The author argues for the use of an abstract base class and I might agree this time. if the classes you are creating have many functions that must be implemented the extra sanity check provided by the abc will be useful. That is to say, if you need to implement a new class, SystemX, down the road, will you remember that it must implement step1 ... stepX without forgetting any? the abc forces you to implement all of the require methods.
The visitor pattern might be ok, but the way he implements it: no fucking way. 19 classes to control 5 peripherals with settings for two people. most classes have only init and one other method. No, no, no.
I would use classes to control the peripherals, but I would have 1 class per peripheral (5 in this example), 1 class for the person, 1 class (maybe) for a controller. Person has all the settings that are then broadcast to the peripherals, the controller could mediate between multiple present persons.
State machine where the states are the people that are home. I.e. P1+P2, P1+!P2, !P1+P2, !P1+!P2, would possibly simplify it farther.
Not much to say about model-view-controller, it feels like common sense to me. Anyone who has used Flask or Django will be familiar with this. Really anyone who has a smidgen of coding sense will recognize that separating responsibility of code is more than a reasonable goal.
Last we have the publisher-subscriber, which is really a rehash of the observer. The irony is how the author goes out of his way, through the whole book to decouple everything, but, and this is a perfect example, you can't totally decouple everything. I think I would go about this a little different, probably approach it from an API standpoint, have the point of contact be the published server where the client subscribers occasionally ping for updates. RSS really.
All in all, I would probably not recommend this book to someone wanting to learn about these patterns. I'd probably call the book intermediate to advanced, but I think the author is forcing python to be Java-like, and I don't condone that.