• Post Reply Bookmark Topic Watch Topic
  • New Topic
programming forums Java Mobile Certification Databases Caching Books Engineering Micro Controllers OS Languages Paradigms IDEs Build Tools Frameworks Application Servers Open Source This Site Careers Other Pie Elite all forums
this forum made possible by our volunteer staff, including ...
Marshals:
  • Tim Cooke
  • Campbell Ritchie
  • Ron McLeod
  • Liutauras Vilda
  • Jeanne Boyarsky
Sheriffs:
  • Junilu Lacar
  • Rob Spoor
  • Paul Clapham
Saloon Keepers:
  • Tim Holloway
  • Tim Moores
  • Jesse Silverman
  • Stephan van Hulst
  • Carey Brown
Bartenders:
  • Al Hobbs
  • Piet Souris
  • Frits Walraven

Interesting implementation of Builder pattern with generics

 
Sheriff
Posts: 16767
281
Mac Android IntelliJ IDE Eclipse IDE Spring Debian Java Ubuntu Linux
  • Likes 1
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
Came across this (really old) blog post just now: https://michid.wordpress.com/2008/08/13/type-safe-builder-pattern-in-java/

There are some interesting parts to it that I'm trying to wrap my head around:

and the example usage:

The declaration on line 5 (in Foo) threw me for a loop and then I saw lines 47 and 48. Still trying to make sense of this. Please help my brain... thanks.
 
Sheriff
Posts: 26943
83
Eclipse IDE Firefox Browser MySQL Database
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
It took me quite a while just to realize that "HA" and "HB" were type variables. I'm not used to code which violates the convention that a type variable is represented by a single capital letter. Using A and B would have been more intuitive. I also had to move the TRUE and FALSE classes outside of the Initializer class so that they could be seen by the compiler. Which I'm also not used to, code which violates the convention for class names; True and False would have been more intuitive.

But mostly my feeling is that the code is a tour-de-force and, like the comments on the blog entry said, doesn't scale to a class with ten variables to be initialized.

 
Junilu Lacar
Sheriff
Posts: 16767
281
Mac Android IntelliJ IDE Eclipse IDE Spring Debian Java Ubuntu Linux
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
I found that blog entry while reading through the Groovy documentation on the Builder transform, specifically, the initializer strategy: https://docs.groovy-lang.org/2.4.8/html/gapi/groovy/transform/builder/InitializerStrategy.html

I'm messing around with Spock tests and got sidetracked on a Yak Shaving expedition.

Reference: my favorite story that actually involves shaving a yak
 
Saloon Keeper
Posts: 13477
304
  • Likes 1
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
This reminds me of Haskell, where some programmers will try to impress each other by writing entire programs using types and type operations only. It invariably leads to an unreadable mess.

I'm all for type-safety, but at some point you're just turning the compiler into an interpreter.

The point of the builder pattern is to make your code more readable by not having a constructor with ten-million parameters. What's the point of making your builder unreadable instead? And besides, if the entire point of a builder is to avoid ten-million constructor parameters, I think an example of this type-safe initializer that manages only 2 parameters hardly suffices.
 
Junilu Lacar
Sheriff
Posts: 16767
281
Mac Android IntelliJ IDE Eclipse IDE Spring Debian Java Ubuntu Linux
  • Likes 1
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator

Stephan van Hulst wrote:This reminds me of Haskell, where some programmers will try to impress each other by writing entire programs using types and type operations only. It invariably leads to an unreadable mess.


Right there with you guys on this, I was just trying to get my head unwrapped from itself after all the hoop jumping the code was making it do trying to understand WTF was going on.
 
Rancher
Posts: 4062
56
  • Likes 2
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
About ten years ago there were a few articles about how Scala's type system is actually Turing complete.  Which in turn implies it's possible to write code that can never finish compiling, because the type system is in an infinite loop.  Not usually considered a desirable property in a compiler. . As I recall, the proof of this relied on similar definitions of types for TRUE and FALSE, as well as types for arbitrary integer values.  Fun.

While it's certainly an interesting idea, yeah, not very fun to read in practice, especially for classes with many fields.  Also, while it's nice to enforce that specific fields are initialized... that's just a subset of the various invariants one might like to enforce when creating instances of a class.  Ultimately with the builder pattern, we can put arbitrarily complex checks inside the build() method, and we have to accept that even if the compiler thinks we have created an instance, the build() method may end up throwing some sort of validation exception.
 
Saloon Keeper
Posts: 1728
63
Eclipse IDE Postgres Database C++ Java
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
I've actually been studying the Builder pattern, among some others, the past few days.

It seems that a good use for Builder is having a couple of initial constructors for combinations of required properties, with the rest being optional add-ons, if there are dependencies across any of the optional parts, it is hard to avoid the throw-at-completion thing, unless each of the optional parts are treated as groups (so you can't ask for any invalid combinations)...I guess like most design patterns it sounds great and there are some applications of it that work nice and others that weren't even a solution in search of a problem, but more like a solution that comes with new problems built in.

 
Jesse Silverman
Saloon Keeper
Posts: 1728
63
Eclipse IDE Postgres Database C++ Java
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
This comment might belong in one of the forums more dedicated to Design Patterns, but we were discussing Builder here.

I am fully aware that Design Patterns are just that, Patterns, not fully detailed recipes or copy-and-paste drop-in solutions.

But how differently different popular YouTube presenters interpret them, after holding up and waving around the same two books (Gang of Four and Head First, almost always the first edition) blew my mind.

I mean, the way they choose to implement them in Java varies by like a factor of ten more than I was expecting.

Inevitably, some are so different that the comments crowd says "That isn't even Builder, it is much more like Factory".

Sure, you get what you pay for (that was my School Motto back at the Cooper Union), so I am not complaining per se.

Just a comment about just how much popular sources distributing free programming/design advice differ in what they think a typical implementation of a particular design pattern might look in Java.  The answer: way more than I was expecting.

I do like having names for things we are going to talk about, so it is nice we have semi-standardized names.
The notion of what is essential in them seems to vary widely enough (at least from stuff I watched) to undermine the value of having common names.

Part of that might be from people looking at 1994/1995 C++ code and deciding how to translate that into Java, but I think I see the same thing coming from people who had read "Head First" (or at least bought a copy and wave it around at the beginning and end of the video)...

Fun stuff.
 
Junilu Lacar
Sheriff
Posts: 16767
281
Mac Android IntelliJ IDE Eclipse IDE Spring Debian Java Ubuntu Linux
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator

Jesse Silverman wrote:Just a comment about just how much popular sources distributing free programming/design advice differ in what they think a typical implementation of a particular design pattern might look in Java.  The answer: way more than I was expecting.


Let's revisit the GoF book and its definition of what a pattern is. It's the nature of a pattern to have many different ways to implement the solution.

Christopher Alexander wrote:Each pattern describes a problem which occurs over and over again in our environment, and then describes the core of the solution to that problem, in such a way that you can use this solution a million times over, without ever doing it the same way twice."


And on the next page from that where that quote appears in the GoF book,

The Gang of Four wrote:The solution doesn't describe a particular concrete design or implementation, because a pattern is like a template that can be applied in many different situations. Instead, the pattern provides an abstract description of a design problem and how a general arrangement of elements (classes and objects in our case) solves it.


Note that it's the problem that occurs over and over. There are countless ways to implement the solution.
 
Jesse Silverman
Saloon Keeper
Posts: 1728
63
Eclipse IDE Postgres Database C++ Java
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
I used to try to explain away my lack of mastery of Design Patterns by saying "They are just workarounds for deficiencies in languages that we use.  I'd rather learn to use the facilities the languages I use provide me than a big catalog of generic ideas."  Pretty lame, but I realized the relative importance of some of them actually do vary from language to language.

Let's look at the Builder pattern.  I was thinking how it would be pretty neat for actually making a pizza, at least at make-your-own places like Blaze, but anyway...

In Python or C#, named or optional/default parameters can make this relatively unexciting.

Sure, in Java we have VarArgs if they are all of the same type, but default or optional parameters would make it a little less exciting, still a drag because they can only be trailing ones, we can't skip in the middle...

Named Parameters seemed horrible to me when I first saw them.
I felt they were exposing internal implementation to the outside world as part of the interface.
Well, they are, but they are convenient as heck.

In Java, with no named parameters and even neither default nor optional parameters, I guess we could make a pizza with a VarArgs of Toppings... but it feels like we would want recourse to the Builder pattern more often than if we had those Python-y options for our method parameters.

I get that maybe I was naive to think that a lot of the wild variation I saw in their implementation was just due to language differences.

In this case, it seems a lot of people like to go for a Java implementation that is "StringBuilder-style", i.e. chainable with each method returning a mutated nascent object, well, with a call to say we are done chained at the end.

But some don't go anywhere near that, they look utterly different.

I will reduce my expectations of how similarly different implementations will turn out, just because they are all in Java.
 
Junilu Lacar
Sheriff
Posts: 16767
281
Mac Android IntelliJ IDE Eclipse IDE Spring Debian Java Ubuntu Linux
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
Check out the Groovy Builder to see examples of different implementations of the same pattern: https://docs.groovy-lang.org/next/html/gapi/groovy/transform/builder/Builder.html

It's actually quite elegant, the whole concept of design patterns, and to me that's a testament to the brilliance of recognizing and articulating the idea. Design patterns are not so much inventions as they are discoveries. The solutions are just different ways of expressing an understanding of the pattern and the problem, and therein lies the beauty of it because each one of us can come up with equally good expressions of the same basic understanding. It takes communication to a whole 'nother level of abstraction.
 
Stephan van Hulst
Saloon Keeper
Posts: 13477
304
  • Likes 1
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator

Jesse Silverman wrote:I realized the relative importance of some of them actually do vary from language to language.


A nice example of this is the Visitor pattern.

Java (as well as most other object oriented languages) employ single dispatch. This means that the implementation to use when you call a method is determined by the runtime type of only the first argument (the object that you're calling the method on). The runtime types of the other arguments don't matter. This makes it difficult to call the correct piece of code when the code depends on the runtime type of not just the object that you're calling the method on, but also one of its arguments.

Some languages have multiple dispatch built right into them, whereby dispatch of a call to a polymorphic function depends on the runtime type of more than one of its arguments. In Java, we have to manually dispatch the call to the correct method implementation either by using the instanceof operator (which is brittle and inelegant), by using pattern matching in switch expressions (which is much more elegant, but still brittle) or by using the Visitor pattern, which is more verbose, but much more robust than the other options.
 
Junilu Lacar
Sheriff
Posts: 16767
281
Mac Android IntelliJ IDE Eclipse IDE Spring Debian Java Ubuntu Linux
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator

Stephan van Hulst wrote:Java (as well as most other object oriented languages) employ single dispatch.


When I think of the Visitor pattern in Java, it's always double dispatch: https://refactoring.guru/design-patterns/visitor-double-dispatch and it has to be done that way because of how the Java compiler does early binding vs late binding. While it works, having to do double dispatch is still rather unwieldy. Groovy, on the other hand, uses runtime binding so I'm guessing (I haven't tried it yet) that it will be much more straightforward. I'll try to come up with a Groovy example of the same scenario given on that website.
 
Stephan van Hulst
Saloon Keeper
Posts: 13477
304
  • Likes 1
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator

Junilu Lacar wrote:When I think of the Visitor pattern in Java, it's always double dispatch


When I said single dispatch, I was referring to Java's virtual method invocation mechanism. The visitor pattern is indeed used to perform double dispatch, albeit manually. This is another form of manual double dispatch:

The equivalent using a visitor:


While the version using the switch expression seems more straightforward, it's also harder to maintain: When you add a new kind of AlgebraicExpression, you need to remember to update all your switch expressions to include the new case. With a visitor, you just update the AlgebraicExpressionVisitor to add a new abstract method, and the compiler will point out all visitor implementations that haven't implemented this case yet.
 
Jesse Silverman
Saloon Keeper
Posts: 1728
63
Eclipse IDE Postgres Database C++ Java
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
I also see why many want to revisit Design Patterns in Modern Java in light of lambdas and streams.

So many more opportunities to easily pass in "just what you want to do" to something that knows how to do "something" to something else.

That this sounds so vague as to be meaningless implies the value of Named Design Patterns.
 
reply
    Bookmark Topic Watch Topic
  • New Topic