So I've been reading the design patterns book.
And like there are some "now this book's not really aimed at me" moments, but it's like fun in a way. Since I work with programming and also with other people, I'm sometimes exposed to a programming idea: It's likely to be a particularly weird idea, probably because it's kind of a copy of a copy of a copy. The original idea tends to be pretty much lost, and what's left is pretty shallow and is probably mostly about like very tiny convenience adjustments.
Like, when people talk about using a builder, chances are it's just about something grotesque like slapping a Lombok @Builder
onto something and save, uh, a number of lines that is proportional to how badly designed the relevant code is. Or at best not that but still just something along the lines of "letting us call the setters one by one before the constructor."
The pattern discussed in the book is for when the building is more complex, and also for something or other with indirection or information hiding or whatever. They have an example that is almost like this:
It has a Director (the RTFReader
) that knows how to supply the stuff needed to build something (like it knows how to parse RTF). But it will let the actual building be done by a Builder (TextConverter
). Client code will set up a Director with a ConcreteBuilder and the Director will call the appropriate Builder methods. The Director only deals with the Builder interface, but the client knows which concrete implementation of the Builder it's dealing with. If the client plugs a TeXConverter
into a RTFReader
and then sets it off to ParseRTF
, then the RTFReader
will call ConvertCharacter
, ConvertFontChange
and ConvertParagraph
when appropriate. While the RTFReader
won't know about the GetTeXText
method, the client can use it after ParseRTF
to get the thing that was built.
The same Director can be used to build different things by plugging in different builders and this also lets us avoid e.g. the same thing being resposible for both "knowing how to read X" and "knowing how to build Y." Blah blah.
So I think that the pattern discussed in the book, compared to what is often called builders these days, is like more interesting. It deals with an actual thing and there is more to it than something like "letting us call some setters before the constructor."
There is this way of being object oriented. This thing were we kind of send a message off and then some communicating objects do some stuff. And I don't necessarily hate that and I guess it can be useful for this or that, but like.
I see this thing and I'm like: Maybe just take TextConverter
and turn it into a sum type? Erase all the "Converter" and "Convert" from the names and have a Text
type with Character(char)
, FontChange(Font)
and Paragraph()
as its summands/union cases. Like in Java, instead of e.g.:
public interface TextConverter {
void convertCharacter(char c);
void convertFontChange(Font f);
void convertParagraph();
}
We could do:
public sealed interface Text {
record Character(char c) implements Text {}
record FontChange(Font f) implements Text {}
record Paragraph() implements Text {}
}
And then return Text
values instead of calling TextConverter
methods. (Maybe call the parse method to get the next Text
value, or have it return a stream of Text
values, or whatever.)
And like, they are two ways of capturing what is mostly the same thing: We have these three cases, character, font change and paragraph. Whether they're three methods on an interface or three union cases of a sum type, that type with the three things is the thing that both sides have to know about here. That's the shared information. The thing where if A and B share an understanding of this, then they don't need to know anything more about each other.
Okay so with the interface with methods on it, we have this very direct and technical connection: A is talking to B. The Director is calling the Builder methods. And then that's a thing we "deal with" by hiding things: The director knows about the Builder interface but the ConcreteBuilder implementaion is hidden from it, because we don't want it to know. It's something I think comes up a fair amount in this kind of "object oriented design." We're introducing this connection and it immediately becomes kind of a "design problem," and it's like oof:
TextWidget
, so we need a new TextConverter
implementation and maybe there are some patterns around for composing thingsIn Enterprise Software Engineering we seem do do a lot of stuff: We're wrapping things, maybe decorating or proxying, bridging and adapting. And that kind of stuff seems to come with those "design problems" pretty often. It might make sense and be reasonably stuff to do anyway, here and there. But I think that with a "returning values" approach you tend to get less of those problems, and that it's often worth considering.
If the reader thing is returning values instead of "directing," then that troublesome connection just isn't there. Things are still connected somehow, but elsewhere, outside of the reader: The client might pass the Text
values to a builder kind of thing. And then if we want to pass it to two builders there doesn't need to be more to it than uh, like, doing that. A unit test can look at the returned Text
values directly without having plugged anything into the reader. And so on.
Mlip mlop.
PS This stuff isn't that much about like, "the book should have said such and such instead of so and so." The book is from 1994 and the code in it is mostly C++ (and some Smalltalk). I'm sure my "use a sum type instead" reaction would make less or no sense in that context.