Skip to content(if available)orjump to list(if available)

The Gang of Four is wrong and you don't understand delegation (2012)

ddellacosta

In my opinion the existence of No true Scotsman posts like this arguing about random concepts is endemic to OOP and a symptom of the lack of formalization inherent in that approach. The GoF book just piles more confusing noise on top of it.

I soured on OOP decades ago exactly because I grew tired of reading dogmatic posts like this or bike-shedding how to do DI or whatever The Right Way with my colleagues. At the end of it I never ended up feeling like I'd ever grasped the core of what OOP actually is, because I don't think there's actually anything there. Maybe there are coherent threads that can tie together languages like Smalltalk, Eiffel, C++, Java, Self, Javascript, Python, Ruby, etc. etc. but I don't think it's a nebulous concept called "OOP," and I think there's more value in talking about how specific features can be used in these different languages on a case-by-case basis than trying to hone in on some coherent, generalized notion of "delegation in OOP."

To be fair the same could be said for FP, but at least with functional programming I can latch onto some core concepts and assert a coherent, formalize-able definition that makes sense across languages, to a greater or lesser extent (again, with debatable value given my last point in the paragraph above). "OOP" as a concept just makes me throw my hands up in the air.

wat10000

"There's nothing there" is pretty close. I see OOP as fundamentally one key concept: that there are functions which "belong to" specific types, and all other operations on those types go through the functions that "belong to" them.

You can do OOP in a language like C by writing your code as structs and accompanying functions that operate on them. You might expose the structs to the rest of your code as opaque pointers to enforce that all other code has to go through these accompanying functions to use this struct. Or you might just make it be convention. An OOP language is just one that has language features to facilitate this, such as by allowing you to define methods within a type rather than functions outside of it.

Once you have this, then you might start thinking of features that might be nice to have alongside, such as dynamic typing, dynamic dispatch, inheritance, etc. But I don't think any of those are fundamentally OOP, they just fit well and are often found together.

Since having functions that belong to specific types isn't a very big concept, discussion about OOP is going to focus on these other things, which are not necessarily omnipresent and are often different across languages. So the discussion is either going to be very specific to one particular language (maybe even one particular set of libraries for it) or very vague.

BerislavLopac

> I see OOP as fundamentally one key concept: that there are functions which "belong to" specific types, and all other operations on those types go through the functions that "belong to" them

I would go even further:

All programming essentially consists of two things only:

  1. data structures (often, but not necessarily, formalised as "types")
  2. mechanisms to transform that data (functions, methods, algorithms, tasks, whatever)
Programming paradigms essentially describe different ways to organise these two: functional programming, for example, keeps the two strictly separate; in OOP the mechanisms are "attached" to the data they operate on. Of course, in practice there are many combinations and variations, with more and less flexibility (from strictly closed objects to mixins and similar), but this is the foundation.

aktenlage

So you say we don't need OOP because you can use this half-baked (non type-safe) way of implementing your favorite OOP feature yourself in your favorite touring-complete language? While omitting the key concept of virtual functions.

rangerelf

Yes.

"OOP" is a philosophy more than specific implementation details. I've programmed "object orientedly" in any number of languages, many of those with no object orientation helping syntax, like C, assembly, Forth (although that one's more malleable). It's always been about the mental model of your programming, anything else is syntax sugar.

All discussion about "your not doing true OOP because $REASON" arguments are useless, repetitive, and discouraging to exploration, discovery and learning.

wat10000

I'm not sure where you got "we don't need" from, unless you consider any language feature to be unnecessary if there's a way to implement the concept without it. Which is not a very useful way of looking at language features! You can implement anything in assembly, that doesn't mean higher-level languages aren't needed.

I'm saying that OOP as a concept is simple enough that you can do the basics of it in pretty idiomatic and straightforward C, and all the other stuff that we associate with "OOP" is not really universal. And thus it's no surprise that discussions around OOP as a general concept tend to be vague and not very useful.

jghn

Why do you seem to feel that OOP defines a specific set of features, for instance virtual functions?

There are many different OOP styles out there. All one needs to do is to look at the languages that originated the concept, such as SIMULA and Smalltalk to see that they weren't channeling the exact same ideas.

kmeisthax

OOP has a "there" there, it's just that the core formalism is user interfaces. If you look up "object oriented programming" on Wikipedia, the first image you get is a UML diagram of a user interface component.

OOP was invented to facilitate old-fashioned "expert systems" type AI research. While that's a nebulous concept[0], it turned out to be really useful to represent (retained-mode) UI in this way. Any mildly complicated UI needs to be a tree structure where the contents of the tree are all different things. You might want to put a button inside of a cell in a scroll list on the right pane of a window, and to do that, all of those things need to...

1. Support arbitrary child objects of different types and data sizes

2. Communicate with a standard set of events and system behaviors

3. But also support modifying those behaviors or introducing new events to fit the present application

Outside of immediate-mode GUIs for games, GUI-approximating text mode applications, and classic Mac OS[1], every UI toolkit worth its salt is OOP. Yes, even USER.dll. Microsoft[2] wrote an object system on top of a non-object-oriented programming language because it was just that useful.

If you aren't doing UI work, OOP loses it's effectiveness. But doing UI work in a non-OOP language is painful, in direct correlation to how non-OOP the language is. e.g. If you're working in Rust, you can probably get by with a bunch of traits and `dyn UiWidget` them; and there might also be something similar for Go; but in C you'd have to manually construct vtables for every object type you plan to include and pass pointers to everything around everywhere.

[0] Yes, the term "AI" has been meaningless for over half a century. Yes, there's probably a link between that meaninglessness and the nebulousness of UI and OOP.

[1] Steve Jobs had been so distracted by the fancy pointer-driven UI at Xerox that he forgot about the OOP language powering it, Smalltalk. He'd fix that immediately at NeXT.

[2] Also, GIMP, whose UI toolkit wound up becoming GTK, and whose object system became GObject.

orwin

Having done UI (and a raytracer) in non-OOP language during my curriculum, I can only strongly agree with parent. OOP is essential to do specific kind of work, including but not limited to UI.

I really hate it to be clear, I dislike the paradigm and find it sometimes difficult to understand. I hate the footguns, I hate inheritance chains. I tolerate mixins and interfaces. But I have to admit: in some cases, OOP is what I need , so I use it.

jayd16

OO is pretty simple. Use objects. There are many many patterns that you can use that rely on this concept. You don't need to use every pattern to be using OO. Even at the most basic level of "a scoped namespace", OO can be useful in organization. The fact it can be used in many ways means there are many perspectives on how it can or should be used.

Popular OO languages are also very pragmatic. That means that even if they are OO languages, they allow for non-OO patterns as well. OO is flexible in this way. A counter example is FP, where it is not very flexible. FP has many rules you must follow to make the concept work, such as how mutability is handled.

chuckadams

I'm no great fan of GoF, it leads off saying to use composition, then most of the patterns just go hog-wild with inheritance anyway. But I don't think anyone has a lock on the term "delegation". It means "pass off responsibility to something else", and it doesn't need to depend on any single mechanism hardwired into the language. Whether `self` stays intact across dispatches or you need to pass `this` explicitly to your delegated handler isn't the central point.

gnoack

The way I interpreted most of these "subclassing" cases in the GoF diagrams was actually as "subtyping", and then it makes more sense.

Regarding no one having a lock on the term "delegation", I think this is spot on. Yes, someone might have used the term differently before GoF, but that does not mean that GoF was wrong. It just meant something else in their context.

(Btw, congratulations, your comment is the first one so far in this comment thread that actually discusses the linked article and not just the GoF book itself.)

atonse

Unrelated to the topic, but I worked with Jim (blog author) over a decade ago and he was an awesome guy to work with. Real standup human being altogether, in addition to writing good ruby.

Jim, if you're lurking around, hi, hope all is well!

pjdesno

I didn't read the GoF book until the early 2000s. I gave up on it after reading their recommendation to use the "state machine pattern" for network protocols- that was a popular approach in the late 70s and early 80s, but by 1990 or so everyone knew the ways in which that was a bad idea. (I worked with some of those state machine implementations in the 80s)

There's a tendency among people who think of themselves as experts in "Software Engineering" to think that domain area expertise is irrelevant. This book is an example of that view.

coherentpony

> everyone knew the ways in which that was a bad idea

The state machine pattern is used currently in verbs. That was the state of the art for many decades before higher level interfaces appeared.

Why was it a bad idea? Can you elaborate?

pjdesno

Do you mean the RDMA verbs API? Or something else? I'll point out that it's an API, so an implementation pattern isn't relevant. (it's also an awfully obscure one, and I say that as someone who was involved in standardizing the pre-Infiniband Virtual Interface Architecture API that it derives from)

It's a relic of the old 7-layer model, where you looked at each layer in isolation, and was ditched in high-performance networking code starting with Van Jacobsen's in maybe 1991. No one ever hit 10mbit/sec with a state machine-coded stack.

Note that very few people write network protocol implementations, so it baffles me why they thought to suggest that their high-level design pattern would be useful in low-level kernel code.

coherentpony

Yes, I meant the RDMA verbs API.

Thanks for the insight.

josefritzishere

Gang of Four was a great band. Solid Gold... top notch record.

weberer

They're both likely a reference to the infamous political faction in China in the 60's.

https://en.wikipedia.org/wiki/Gang_of_Four

pjdesno

"likely" -> "certainly". At the time the band was formed, the Gang of Four was in the news because they were being brought to trial in China.

About the only GoF song that wasn't extremely political was their one hit, "I found that essence rare". Kind of like Chumbawumba - albums full of political songs, but the one that charted was the mostly apolitical one.

seanwilson

When you code in a functional style (i.e. not OOP, passing functions as arguments, avoiding mutable state), which Design Patterns are still useful/interesting?

singron

Trivial Builders are often used in java due to lack of named parameters or object literals. Trivial Visitors are sometimes used due to the anemic switch/enum support, although it's gotten a little better with switch expressions and switch patterns. Singletons and factories are used since java metaprogramming usually requires an instance of an class and can't use the class itself. E.g. you can only call interface methods on an instance, unlike e.g. a rust trait where you can call a trait method on a type without an instance of that type.

In non-trivial cases, these are still useful patterns though in many languages. E.g. traversing a complex AST is often an ideal case for a Visitor. Builders are useful e.g. you want to validate relationships between objects, so you keep a hashmap while building so you can do O(1) validation while building and provide targeted error messages, but then throw away the hashmap after building. Builders are especially useful if you avoid mutable state since you can often contain the mutable parts to the Builder and make the built object entirely immutable.

A lot of the patterns are useful, but you would unknowingly "use" them when appropriate without ever having explicitly learned them. E.g. the Adapter pattern is an obvious solution to a particular problem. I think the only contribution the book has for these patterns is giving these names.

yxhuvud

All languages have patterns. They differ some between languages as language features dictate what problems need conceptualized solutions that are different from what the language provides. There are also lots and lots of domain specific patterns that are not very documented but which people that work within a domain tend to recognize.

stickfigure

It's been a while so I just looked through the list and... pretty much all of them?

It would be easier to identify the ones that don't make sense. The only obvious one is Observer, since without mutable state there's nothing to observe. Everything else looks relevant for immutable objects.

jerf

Alternatively, if you think of the patterns not as names of things, but as the actual solutions presented in the design patterns book, none of them. None of them can be implemented in a non-class language when all the patterns are intrinsically class oriented, and quite heavily so, often actually dependent on inheritance.

Not saying this is the only way of viewing them; there is validity to both but it is I think it is important to keep in mind that the patterns are also supposed to be solutions and not just the names of problems.

What happens as you move around languages is not so much a binary thing, but the relative importance of them changes. Plus you should expect patterns to appear in a new language that weren't in the old language, both because the pattern would be too easy to be a pattern and too difficult to be a pattern. Some will fade away so far that maybe they aren't useful any more.

I really know someone doesn't get the GoF pattern thing when I see "Design Patterns in X" for X != ["Scheme", "C++"] and they are exactly and only the GoF design patterns, implemented in the target language. That is not what they are for. Generally in my experience they're some combination of "wrong", "unidiomatic", and "not the easiest alternative" in the target language anyhow.

AnimalMuppet

You need a different list of patterns.

Arguably, Haskell's use of monads is a pattern. It just isn't in the GoF, since it doesn't solve a problem that was a problem for the languages the GoF was written for.

jghn

> passing functions as arguments

You mean the Strategy Pattern?

A lot of the patterns are still there. They just look very different.

thefourthchime

Unpopular opinion, but I think the Design Pattern book created more problems than it solved. It encouraged people to come up with clever designs for object-oriented systems. Object-oriented systems themselves tend to get too complicated. And then making people think they have to use some kind of smart design encourages an addition of complexity.

The first rule of design should be make it as simple as possible. And this book, I think, does the opposite for many people.

AndrewStephens

I don't think that is an unpopular opinion at all, and I like OO programming just fine. People latched onto Design Patterns like that were tablets from God whereas the book was better thought of as giving a taxonomy to existing practices (and not even best practices.) Different subsets of their patterns work better in different languages, and it is pointless to force a specific pattern into a design where it doesn't fit naturally.

I think it is useful to say "This design could be thought of as a delegate" when explaining your code, but saying "We need a delegate design pattern here" is needlessly prescriptive.

alexjplant

> People latched onto Design Patterns like that were tablets from God whereas the book was better thought of as giving a taxonomy to existing practices (and not even best practices.) Different subsets of their patterns work better in different languages, and it is pointless to force a specific pattern into a design where it doesn't fit naturally.

> I think it is useful to say "This design could be thought of as a delegate" when explaining your code, but saying "We need a delegate design pattern here" is needlessly prescriptive.

1000% this. The authors of the book describe it as a catalog of existing solutions to be applied appropriately given specific criteria, not as a panacea to all engineering problems. I've worked with a few architectural prescriptivists who shoehorned everything into a GoF Design Pattern - and amusingly enough none of them had actually read it and so could only name the common ones like Repository, Factory, or Singleton. Everything that wasn't one of these was just a Facade as far as they were concerned. This definitely worked but it left much to be desired once you started venturing out of rote CRUD territory.

On the other hand I had one Team Lead that shunned such tradition and purposefully mashed several things I'd written (Facotries, Repositories, and a Service that encapsulated everything) into a single giant "Helper" class destroying any abstraction or separation of concerns in the process. I guess it made little difference because that team never wrote unit tests but it wasn't good design by any stretch.

I digress but the idea is thus: some things in our field are common and timeless. It's helpful to give them names and to understand what the historical pain points are. It's not helpful to use them as a whooping stick.

shagie

What it was trying to be was misunderstood. Arguably, there were too few patterns and they were provided as a way to build software like LEGOs.

I've read "A Pattern Language: Towns, Buildings, Construction" which was the inspiration for the GoF book. That book had 253 patterns and they were written as "here are ways architects solve common problems". Pattern 148 - small work groups

> When more than half a dozen people work in the same place, it is essential that they not be forced to work in one huge undifferentiated space, but that instead, they can divide their workspace up, and so form smaller groups.

> In fact, people will feel oppressed, both when they are either working in an undifferentiated mass of workers and when they are forced to work in isolation. The small group achieves a nice balance between the one extreme in which there are so many people, that there is no opportunity for an intimate social structure to develop, and the other extreme in which there are so few, that the possibility of social groups does not occur at all.

> ...

> Break institutions into small, spatially identifiable work groups, with less than half a dozen people in each. Arrange these work groups so that each person is in at least partial view of the other members of his own group; and arrange several groups in such a way that they share a common entrance, food, office equipment, drinking fountains, bathrooms.

> Lay the workgroups out with respect to each other so that the distances between groups is within the constraints of OFFICE CONNECTIONS (82), and give each group office space which leaves room to expand and to contract-FLEXIBLE OFFICE SPACE (146); provide a common area, either for the group itself or for several groups together or both —- COMMON AREA AT THE HEART (129). Treat each small work group, in every kind of industry and office, as a place of learning—MASTER AND APPRENTICES (83). Give it its own stair, directly to the street—OPEN STAIRS (158). Arrange the individual workspaces within the small work group according to HALF-PRIVATE OFFICE 152) and WORKSPACE ENCLOSURE (183) . . . .

It's a not a dictate, but rather descriptive of "these are problems and ways people solved them" along with maintaining a unified vision of the design.

The GoF book became prescriptive instead. People used the patterns in anticipation of the problems rather than because they had them.

https://www.artima.com/articles/how-to-use-design-patterns

> Bill Venners: Is the value of patterns, then, that in the real world when I feel a particular kind of pain I'll be able to reach for a known solution?

> Erich Gamma: This is definitely the way I'd recommend that people use patterns. Do not start immediately throwing patterns into a design, but use them as you go and understand more of the problem. Because of this I really like to use patterns after the fact, refactoring to patterns. One comment I saw in a news group just after patterns started to become more popular was someone claiming that in a particular program they tried to use all 23 GoF patterns. They said they had failed, because they were only able to use 20. They hoped the client would call them again to come back again so maybe they could squeeze in the other 3.

> Trying to use all the patterns is a bad thing, because you will end up with synthetic designs—speculative designs that have flexibility that no one needs. These days software is too complex. We can't afford to speculate what else it should do. We need to really focus on what it needs. That's why I like refactoring to patterns. People should learn that when they have a particular kind of problem or code smell, as people call it these days, they can go to their patterns toolbox to find a solution.

---

The Patterns from GoF became the goal without understanding what a Pattern is. It's a way to hold complexity. A function call is a Pattern. Store volatile registers onto stack, put return address onto stack, put parameters onto stack, jump to new instruction, restore registers.

That Pattern is now hidden inside of `foo(bar, qux)` and that hides a lot of the complexity for what is being done.

But we don't come with the idea that to write a good program, you must use functions. Well, you should because they're likely complex enough to need them... but we use them when we need to manage that complexity.

A phrase I used before was that the GoF isn't a cookbook but rather a bestiary of complexity.

The problem is when people use Patterns in anticipation of complexity that isn't there. So when they're putting patterns in places, as a maintainer you see dark and (maybe) empty cages. Do you want to stick your hand in it to see if it will get bitten off?

Don't have empty complexity cages. And when you do need the complexity cage, properly illuminate it with a clear description of what it is and what complexity it contains.

(yes, I can rant about Patterns)

https://c2.com/ppr/wiki/WikiPagesAboutWhatArePatterns/Patter...

And one of the blog posts of yesteryear that influenced me - https://perl.plover.com/yak/design/

decompiled_dev

I think design patterns should be more descriptive, but people try to use them prescriptively.

Being able to say "circuit breaker" pattern or "visitor" is a time efficient way to communicate complicated ideas. Its value is being able to conserve the limited bandwidth speaking has.

Bad coders will always find something or another to justify their work with and it just so happens the most popular book is often referenced, for good and bad.

reval

Both of you nailed it. I’ve always used the term “implied implementation” to describe the phenomenon where a description becomes prescription.

vacuity

You're right, but the key thing about description and prescription is that they aren't really separate. You constantly have to ask "does this work well here?", "should I replicate it there?", and so on. They deal with the same thing in different modes of interaction, and so being able to structure these analyses is important to doing each well.

zdragnar

You'd be surprised at how often seniors don't actually ask those questions when it comes to prescriptive standards. They get used first and questions only come up when it's too late.

RamblingCTO

100% agree. The worst offenders imho are DRY and inheritance. In most cases it causes garbage code that adheres to some lofty goals but sucks to be maintained. We actually do the opposite of DRY: isolate behaviour, even when shared. If complex enough and shared: build a lib. But one use case = one app functionality. We are so happy and productive with that. Throw in ports + adapters and use cases (= one feature) and you're set.

wakawaka28

You know what sucks more than fixing problems with inheritance? Fixing 20 different copies of the same bug, or just having to copy over dozens of methods in the first place. If you feel like inheritance is wrong, it's probably because you've got too many corner cases and too many responsibilities shoved into a class. Composition can help in such cases, but there's maybe no magic pattern if each object is too different from the rest.

azaras

But GoG saids to use composition over inheritance

bryanlarsen

AFAICT, I think your opinion is fairly widely shared.

jghn

Yes. It's become a popular opinion over time. People lauding GoF today should be viewed with skepticism.

inanutshellus

IMO this is instead a mark of its ubiquitousness in our industry.

It's been so successful that the only thing left to do is talk about scenarios in which it doesn't work.

Reminiscent of the Dark Knight quote -

"You either die a hero, or you live long enough to see yourself become the villain."

AnimalMuppet

I think GoF is all right, when viewed as a book of gadgets. That is, if you have this problem, then consider this mechanism as a fix.

The problem is, people treat it like a "here's how to do OO design" book. It is not suited to that, and especially not suited to being the one and only OO design book that people read.

IncreasePosts

Actually, I think it's the other way around. Trashing the GoF is a sign of a blind trend follower. Someone who lauds them are bucking the trend and might have interesting things to say about it.

BerislavLopac

In my experience, design patterns cause a lot of problems when developers reach the stage when they understand them enough to know how to implement them, but not when (and even more importantly, when not).

Most design patterns were invented as workarounds to specifics and limitations of certain languages/paradigms, and work great in that case -- but are much more limited, or even downright dangerous, when used in other contexts. As a clear example, the GoF patterns focus on strongly typed OOP languages like Java or C++, and will result in difficult to maintain bloatware if copied directly to dynamic languages like Python, Javascript or PHP (source: been there, done that, got the t-shirt).

fforflo

I tend to agree. The OOP-all-the-things mantra created many problems; lots of books were printed (more than necessary), and lots of evangelists made their name on unnecessary abstractions over abstractions. One might blame Java for that, but it's a chicken-and-egg problem.

wakawaka28

I think you underestimate how chaotic things would get without some basic concepts like patterns laid out. Like so many things in this industry, disproportionate attention is drawn to perceived problems rather than things that quietly work well with the same tech.

moomin

“Hey, I mean this word to mean something different from you and therefore you are wrong; I am very intelligent.” is a genre of post I am very tired of reading.

Bonus points for making it about a dispatch syntax built into remarkably few languages because, it turns out, it’s painful to use in practice.

globnomulous

Likewise. I also hate the corollary, in non-technical, philosophical conversations: changing the meaning of the word in the belief that doing so somehow confers or demonstrates insight or understading.