I'd hate to be the Gang of Four, especially since they wrote their book 20 years ago. Since then numerous people (including myself) have discussed how to evolve some of their ideas given today's landscape.
My position is many ideas are still valid in their book, but they are less novel today and are often implemented with simpler tools. I'm sure the Gang would concur.
Other ideas may be less valid over time and I'd be surprised if the team didn't grow in their perspective.
I forget who said it, but I ascribe to the notion that most design patterns exist to work around insufficiencies in languages or ecosystems. To that end, a lot of them totally still exist--they've just been reduced in difficulty and ceremony, as you mention. The command pattern, for example, is literally just a closure.
I'm pretty sure this is the case. I think that the ideas are great in producing a vocabulary for classes of structures within software architecture the way people creating a car can talk about gears, rotors, pistons, gaskets.
Whether or not you have the right rotor/gears/gasket/piston is up to the specific case. And sure some of those things may become like carburetors, and may become largely obsolete. But there is definitely a value in having a consistent vocabulary for things like "That box that controls the air-fuel-ratio mixture before it goes into the cylinder."
Same problem a lot of good ideas have (like unit testing or agile), an idea meant to be evolved and applied thoughtfully instead becomes ossified instantly and applied mindlessly, with many mistakes, and zealously.
The concept of design patterns is highly useful. The particular patterns in one book would always be of limited usefulness. Often times devs are more eager to prove their smarts than to get a job done well.
> ...an idea meant to be evolved and applied thoughtfully instead becomes ossified instantly and applied mindlessly, with many mistakes, and zealously.
That's such a good quote, I just may copy + paste it mindlessly into my next presentation and assert it with zeal. (With credit of course)
> If the message is delegated further, all questions about the values of variables or requests to reply to messages are all inferred to the object that delegated the message in the first place.
I'm not sure how the author jumps from that quote to:
> When that object delegates to another, then any reference to "self" always refers to the original message recipient. Always.
The high-order bit is that the delegating object can be interrogated for more information or context (and a reference to that object can be forwarded to further delegates), no? Why does that need to be implemented by re-binding whatever keyword or name an object calls itself to a different object?
That is probably because what he and we are talking about here is the technical definition of "delegation".
If you just call a method of another object and then that object perhaps but not necessarily sends some messages back to the original sender, to get further information, that could conceptually be seen as "delegation". But technically that's just normal (bi-directional) message-exchange.
"Delegation" needs to refer to something more technically specific to warrant its existence as a technical concept.
For languages which didn't have function-pointers as a first-class construct (like Java), you had to create another way to delegate functions between different objects. A way to encapsulate them, if you like.
It may not seem novel now, but it was not obvious to everyone at that time that this was a possible design-pattern.
From what I can tell, this is a whole lot of bickering about the difference between prototypical languages (like JS) and inheritance-based OO-languages (like C++/Java/C# etc).
The Gand of four wrote Design patterns for the inhertance-based world, because that was what was churning out most business software and had the biggest need for some standard patterns and conventions. And they used (reused?) the word delegation to fit a useful pattern in a non-prototypical language.
And AFACT that's his argument. That all modern OOP languages got it wrong. So they got delegation wrong too. And GOF got it wrong because the wrote it for modern OOP.
I had to take a couple of passes at that to figure out what the various referents were. This is just a summary of Lieberman's position, so I guess we will have to go to his paper to see the reasoning behind this position.
As far as I follow it, the argument is that delegation means, in prototype-based OO (apparently the only context in which either author recognizes its use), that if an object has no method for handling a message that it has received, then it transitively searches through its prototypes for one that can handle it, and if found, the receiving object delegates the message to that prototype's method for handling. When that method handles it, however, it should do so in a way that is aware of the context of the object originally receiving the message, so that, for example, if both the receiving and handling object have a name, the handler should use the receiver's name if it needs a name in handling the message.
If we assume that the methods reference the context they are working with through a variable called 'self', then passing the handling method a 'self' bound to the original message receiver (not necessarily the most recent delegator) will achieve this: any method calls or references the handler needs to make will go through the same delegation process, starting from the original receiver, and in so doing possibly, but not necessarily, ending up at a method from the same object as was picked to handle the original message.
There may be the additional implication that this greatly facilitates composition, which is (it is claimed) preferable to inheritance, but I would think name collisions are a problem.
Whole OOP is "wrong". It was "right" up to about early 90ies, given the average application size and hardware. The moment CPU's started scaling with cores and CPU started running hundreds of instructions per memory read, and applications started being bigger than few tens of KB - cornerstones of OOP (dynamic polymorphism, code attached to data, per-object access control) started actively working against developers.
It is not "wrong", but it is being "overused". I had been fixing people their broken OOP code for about 10 years (diamond problem, spaghetti code, too much coupling etc.). People seem to abuse it in every way they can. I personally do not like how code is often not separate from the data as well.
That is why i like C. I can have my data structure and just add functions that can operate on those data structures outside of the data structure itself without worrying about coupling code to the data. Extra functions that can operate on the data may as well be inside a dynamically loaded plugin. Not feeling forced to put everything that can operate on a single instance of the data inside the same class. Not feeling forced to create a separate class to add methods that can operate on multiple instances of the data and hardcoding multicore support inside of it.
The new C++ (and Nim) have the notion of concepts which can mostly replace the many ways OOP was being abused. It is similar to type classes in Haskell. I advice you to read Alexander Stepanov's books.
You can have a data structure in a class, add certain concepts that the data structure supports and then create separate functions which can operate on these concepts outside of the class / object.
I'm not sure I'd agree with initial statement (and by this I literally mean - "I am not sure", its not a figure of speech).
I agree with everything else you said, but the way I see it - when a paradigm cornerstone itself starts getting in the way of code organization, then that's a clear sign that paradigm itself is 'wrong', or more precisely, wrong when applied to this set of problems.
Oooooooh, look at that, I understand now what you meant :-D
Regarding your second paragraph - this is, looking from efficiency standpoint, the best approach. Additional gain is in access control: it becomes flat (ie. module-controlled), rather than having a class hierarchy in between, and then using messy constructs such as interfaces/mixins to achieve both code and type inheritance.
Raise hands if you ever ended up in situation where you have to convert from one type to another, while the actual data they carry are precisely the same. Raise hands if you ever had to pollute an interface or a parent class with extra data, because a subclass somewhere contains exact data needed at the other part of the chain.
This is friction - and it works against the developer/team. The larger the software, the worse it gets; it doesn't have to be that way.
Anyways, regarding last two points - yes, I'm very familiar with parametric polymorphism (it is a sole reason I use C++ for work, over C), and I have than half a decade of Haskell experience behind me. Trying to shift into Rust lately - its easier to find jobs.
I think the example gets bogged down in language features which obscures the conceptual idea.
Hence I think the example would best be illustrated using pseudocode, which would perhaps motivate a meaningful discussion of how language features make certain patterns more or less useful, and perhaps, zooming out, certain designs more or less easy to reason about.
The Gang of Four book is terrible. The explanation are very unclear (often because of excessive abstraction, even in the description and discussion).
It's been a long time so my memory is hazy on its particular sins, but I've vowed never to touch it again. I never understood what anyone saw in that book (except that it was one of the first book on the topic of design patterns).
I don't agree. It's a seminal book which, in spite of having been released about 2 decades ago, is still required reading. The book also is well suited as a reference due to their clear explanations, descriptions, and use-cases.
I always thought I was too dumb to understand the Gang of Four book. I thought everyone else must be so much smarter than me, because everyone praised this book. I really had trouble getting a clear picture in my head of the ideas, concepts and how it all fits together, due to the, as you say, excessive abstraction.
I guess it was mostly due to my inexperience in software design in general. I haven't tried to read it again since then, but I'm sure by now I'd at least understand some of the words in there :) So I wouldn't recommend it for beginners, but I think even experienced developers could still learn something new.
I had the same thing. Fortunately there are other books that explain design patterns in a clearer way, but I still didn't quite see the point at the time. Clearly design patterns are important, and I should master them, right?
But then I switched from Java to Ruby, and half of the design patterns were completely irrelevant. And I had other problems for which I didn't have appropriate design patterns.
I think the Gang of Four book's target audience is far narrower than it's often been interpreted. It's meant for intermediate C++ programmers. People who have already run into a number of these problems that C++ doesn't really handle well, and now here's a book that helps you around those limitations.
I guess it also works well for older Java versions, but it's not universal. Many patterns are completely irrelevant for some languages, because those languages have easier ways to do that. Many languages have other shortcomings that are not handled in that book.
Maybe there's another target audience for that book: language designers. Design your language so nobody needs these patterns. Have built-in constructs that handle this stuff in an easy way for the programmer.
I found it easy to understand and I knew nothing about small talk (examples were in smalltalk).
I found it helpful - it allowed me to think and talk about structures in more explicit way, so problem solving got easier.
I think it should be read when you already have some experience with project that has some logic in it (e.g. not crud and also not frontend - frontend requires entirely different structures imo) where you had to make decisions about what goes where. It cease to be abstract then (or your brain adjusted to thinking in more structural way).
Perhaps a better way to explain "delegation" would be to say that it means "borrowing a method from some other object then executing it in the context of the borrower.
In O-O the "context" in particular refers to the "message recipient" available as the value of the pseudo-variable 'this' (or 'self' in Smalltalk).
To delegate = To borrow a method.
Delegator = Borrower.
Delegate = Lender.
I guess that the meaning of the word delegation changed between 1986 and now, definitely in the context of languages that are not prototypal.
I have heard the word delegation as in "this object methid here just returns value somebody else calculated" so many times, that I am pretty confident everybody would be just confused if I started to use different terms.
25 comments
[ 2.9 ms ] story [ 45.4 ms ] threadMy position is many ideas are still valid in their book, but they are less novel today and are often implemented with simpler tools. I'm sure the Gang would concur.
Other ideas may be less valid over time and I'd be surprised if the team didn't grow in their perspective.
I'm pretty sure this is the case. I think that the ideas are great in producing a vocabulary for classes of structures within software architecture the way people creating a car can talk about gears, rotors, pistons, gaskets.
Whether or not you have the right rotor/gears/gasket/piston is up to the specific case. And sure some of those things may become like carburetors, and may become largely obsolete. But there is definitely a value in having a consistent vocabulary for things like "That box that controls the air-fuel-ratio mixture before it goes into the cylinder."
The concept of design patterns is highly useful. The particular patterns in one book would always be of limited usefulness. Often times devs are more eager to prove their smarts than to get a job done well.
That's such a good quote, I just may copy + paste it mindlessly into my next presentation and assert it with zeal. (With credit of course)
I'm not sure how the author jumps from that quote to:
> When that object delegates to another, then any reference to "self" always refers to the original message recipient. Always.
The high-order bit is that the delegating object can be interrogated for more information or context (and a reference to that object can be forwarded to further delegates), no? Why does that need to be implemented by re-binding whatever keyword or name an object calls itself to a different object?
If you just call a method of another object and then that object perhaps but not necessarily sends some messages back to the original sender, to get further information, that could conceptually be seen as "delegation". But technically that's just normal (bi-directional) message-exchange.
"Delegation" needs to refer to something more technically specific to warrant its existence as a technical concept.
It may not seem novel now, but it was not obvious to everyone at that time that this was a possible design-pattern.
The Gand of four wrote Design patterns for the inhertance-based world, because that was what was churning out most business software and had the biggest need for some standard patterns and conventions. And they used (reused?) the word delegation to fit a useful pattern in a non-prototypical language.
And AFACT that's his argument. That all modern OOP languages got it wrong. So they got delegation wrong too. And GOF got it wrong because the wrote it for modern OOP.
Or did I miss anything?
As far as I follow it, the argument is that delegation means, in prototype-based OO (apparently the only context in which either author recognizes its use), that if an object has no method for handling a message that it has received, then it transitively searches through its prototypes for one that can handle it, and if found, the receiving object delegates the message to that prototype's method for handling. When that method handles it, however, it should do so in a way that is aware of the context of the object originally receiving the message, so that, for example, if both the receiving and handling object have a name, the handler should use the receiver's name if it needs a name in handling the message.
If we assume that the methods reference the context they are working with through a variable called 'self', then passing the handling method a 'self' bound to the original message receiver (not necessarily the most recent delegator) will achieve this: any method calls or references the handler needs to make will go through the same delegation process, starting from the original receiver, and in so doing possibly, but not necessarily, ending up at a method from the same object as was picked to handle the original message.
There may be the additional implication that this greatly facilitates composition, which is (it is claimed) preferable to inheritance, but I would think name collisions are a problem.
That is why i like C. I can have my data structure and just add functions that can operate on those data structures outside of the data structure itself without worrying about coupling code to the data. Extra functions that can operate on the data may as well be inside a dynamically loaded plugin. Not feeling forced to put everything that can operate on a single instance of the data inside the same class. Not feeling forced to create a separate class to add methods that can operate on multiple instances of the data and hardcoding multicore support inside of it.
The new C++ (and Nim) have the notion of concepts which can mostly replace the many ways OOP was being abused. It is similar to type classes in Haskell. I advice you to read Alexander Stepanov's books.
You can have a data structure in a class, add certain concepts that the data structure supports and then create separate functions which can operate on these concepts outside of the class / object.
I agree with everything else you said, but the way I see it - when a paradigm cornerstone itself starts getting in the way of code organization, then that's a clear sign that paradigm itself is 'wrong', or more precisely, wrong when applied to this set of problems.
Oooooooh, look at that, I understand now what you meant :-D
Regarding your second paragraph - this is, looking from efficiency standpoint, the best approach. Additional gain is in access control: it becomes flat (ie. module-controlled), rather than having a class hierarchy in between, and then using messy constructs such as interfaces/mixins to achieve both code and type inheritance. Raise hands if you ever ended up in situation where you have to convert from one type to another, while the actual data they carry are precisely the same. Raise hands if you ever had to pollute an interface or a parent class with extra data, because a subclass somewhere contains exact data needed at the other part of the chain. This is friction - and it works against the developer/team. The larger the software, the worse it gets; it doesn't have to be that way.
Anyways, regarding last two points - yes, I'm very familiar with parametric polymorphism (it is a sole reason I use C++ for work, over C), and I have than half a decade of Haskell experience behind me. Trying to shift into Rust lately - its easier to find jobs.
Hence I think the example would best be illustrated using pseudocode, which would perhaps motivate a meaningful discussion of how language features make certain patterns more or less useful, and perhaps, zooming out, certain designs more or less easy to reason about.
It's been a long time so my memory is hazy on its particular sins, but I've vowed never to touch it again. I never understood what anyone saw in that book (except that it was one of the first book on the topic of design patterns).
I don't agree. It's a seminal book which, in spite of having been released about 2 decades ago, is still required reading. The book also is well suited as a reference due to their clear explanations, descriptions, and use-cases.
I guess it was mostly due to my inexperience in software design in general. I haven't tried to read it again since then, but I'm sure by now I'd at least understand some of the words in there :) So I wouldn't recommend it for beginners, but I think even experienced developers could still learn something new.
But then I switched from Java to Ruby, and half of the design patterns were completely irrelevant. And I had other problems for which I didn't have appropriate design patterns.
I think the Gang of Four book's target audience is far narrower than it's often been interpreted. It's meant for intermediate C++ programmers. People who have already run into a number of these problems that C++ doesn't really handle well, and now here's a book that helps you around those limitations.
I guess it also works well for older Java versions, but it's not universal. Many patterns are completely irrelevant for some languages, because those languages have easier ways to do that. Many languages have other shortcomings that are not handled in that book.
Maybe there's another target audience for that book: language designers. Design your language so nobody needs these patterns. Have built-in constructs that handle this stuff in an easy way for the programmer.
I found it helpful - it allowed me to think and talk about structures in more explicit way, so problem solving got easier.
I think it should be read when you already have some experience with project that has some logic in it (e.g. not crud and also not frontend - frontend requires entirely different structures imo) where you had to make decisions about what goes where. It cease to be abstract then (or your brain adjusted to thinking in more structural way).
In O-O the "context" in particular refers to the "message recipient" available as the value of the pseudo-variable 'this' (or 'self' in Smalltalk).
To delegate = To borrow a method. Delegator = Borrower. Delegate = Lender.
Sure it can. The syntax is a little different, though. And there are a few ways to do it. Here's one:
I have heard the word delegation as in "this object methid here just returns value somebody else calculated" so many times, that I am pretty confident everybody would be just confused if I started to use different terms.
Anyone understand the concept?