[0] https://twitter.com/ramalhoorg/status/960746929025142784?s=2...
IMO GoF has been obsolete for the last 20 years (50 years if you consider that languages like ML, Lisp and friends already existed then).
No. The implementation of some DPs is trivial in some languages, but the DPs don't disappear. The idea of DPs primarily as boilerplate implementation recipes rather than solution concepts with rationales comes from the limitations of Java, etc.,. not the DPs themselves.
Do we have special names for storing strings in data structures?
Do we have special names for storing lists in data structures?
Do we have special names for storing source code as strings in data structures?
Do we have special names for storing abstract syntax trees as data structures?
Do we have special names for storing compiled code as data structures?
Do we have special names for storing pairs of compiled code and related memory in data structures?
Why do we need special names for storing functions in data structures?
When I filter the list of numbers it's not a strategy pattern. Neither is when I filter the string list, or the AST list, or the compiled code+closure list. But, somehow, if it's a function list then it's a strategy pattern.
Free your mind of unnecessary labels.
Yes, we do. For instance, if I store numbers that describe a limit, I'll call it a limit. And it will give a hint how it is used. Do I call all numbers in a data structure limit? Of course not - the same is true for functions in datastructures.
> Why do we need special names for storing functions in data structures?
Well, it is helpful. There are many reasons to store functions in datastructures. For example to do mappings, to cache/memoize them or even to create interpretable datastructures, using free monads.
But the strategy pattern describes the situation where there are multiple functions and they all serve the same purpose and have the same interface (and thus are interchangeble) and usually one is chosen and applied - but it is expected to be different ones, depending on the context.
Do we really need a name for that or use it in code? I don't know. But that's not the my point - which is that the concept doesn't go away just because it is easier to use it.
> When I filter the list of numbers it's not a strategy pattern. Neither is when I filter the string list, or the AST list, or the compiled code+closure list.
Sure, if you don't change the way you filter then it's not a strategy pattern.
> But, somehow, if it's a function list then it's a strategy pattern.
Now you have lost me. Maybe we disagree because we are talking about a different thing?. A list of functions does not make a strategy pattern. And very often, strategies are implemented using inheritance (or composition) without any lists being involved. At least that's how I remeber the pattern when I saw it (in the pure functional programming world we indeed rarely call it like that) and how it's written in the GoF book.
Why it is so different to having a bunch of numbers in a list and choosing one of these numbers via some criteria, then doing one of the allowed operations for that number?
Bear in mind with the function I'm doing exactly the same: do one of the allowed operations for it (invoke the function).
Well, you can argue that there are also different numbers and you can do devision only by some of them. But in fact, if you were to separate them and wrap them to do things differently, then this would get closer to the strategy pattern.
And if you do have those problems, it isn't even necessarily what you would reach for first. Though it's at least on the list.
I see people claim that GoF patterns are obsolete, and that builder is one of the ones that are old hat, but for making configurations readable it can be the best solution.
t = TemplateBuilder() .withFoo(foo) .withBar(bar) .excludeBaz() .build()
over:
t = Template(foo=foo, bar=bar, baz=none)
ipv4 = IPAddress().v4("127.0.0.1").build()
ipv6 = IPAddress().v6("::1").build()
Trying to perform something like this in Python with merely a constructor on an IPAddress type would lead to a not-so-obvious interface (such as use X set of named args to construct a v4 address or use Y set of named args to construct a v6 address, but using X & Y in the same constructor is a runtime error). This is my contrived example for where the builder pattern might make sense.With your example, I don't see any compelling advantage to use the builder pattern. In fact, I'd argue as-is, the builder is unnecessary, is more verbose, adds additional complexity, and you should just use the constructor with named args instead.
The point behind the builder pattern is to perform nontrivial initialization that is hard/difficult/impossible to perform in a normal constructor a la C++, C#, Java, etc. If you're not performing nontrivial initialization, you've just introduced extra verbosity and complexity for no gain, so don't use the pattern then. This is, of course, just my opinion.
For example, if you see code that has a lot of overloaded constructors followed by an `init` function that must be called after construction, or code with a lot of overloaded `init`-type functions, only one of which shall be called; the builder pattern might make sense there.
ipv4 = IPAddress.v4("127.0.0.1")
ipv6 = IPAddress.v6("::1")
KISS.That is just horrible...
> maybe parts of the construction might be conditional
No builder pattern needed for that, just use optional arguments and/or an option/nullable type.
I'll post an example in a bit of what I mean, but using a builder lets me use functions to break down the template into readable pieces.
And I'm not saying that it is impossible, I'm talking about readability.