Subclassing with Bloch’s Builder pattern, revised

So last year I posted about using Joshua Bloch’s Builder pattern to create objects from a hierarchy of subclasses.

I realized later that while I did have something like this completely working at one point, I had it working in C#, which has deceptively different generic semantics. (It also has named parameters, which makes the Builder pattern a fair bit less necessary.) As Zdenek Henek demonstrated (several weeks ago, sorry Zdenek!), in Java, the original version I posted doesn’t allow you to call the arguments in any order.

This one does:

class Shape
{
    private final double opacity;

    public double getOpacity ()
    {
        return opacity;
    }

    public static abstract class ShapeBuilder<S extends Shape, B extends ShapeBuilder<S, B>>
    {

        private double opacity;

        @SuppressWarnings( "unchecked" )
        public B opacity ( double opacity )
        {
            this.opacity = opacity;
            return (B) this;
        }

        public abstract S build ();
    }

    private static class DefaultShapeBuilder extends ShapeBuilder<Shape, DefaultShapeBuilder>
    {
        @Override
        public Shape build ()
        {
            return new Shape( this );
        }
    }

    public static ShapeBuilder<?, ?> builder ()
    {
        return new DefaultShapeBuilder();
    }

    protected Shape ( ShapeBuilder<?, ?> builder )
    {
        this.opacity = builder.opacity;
    }
}

class Rectangle extends Shape
{

    private final double height;
    private final double width;

    public double getHeight ()
    {
        return height;
    }

    public double getWidth ()
    {
        return width;
    }

    public static abstract class RectangleBuilder<S extends Rectangle, B extends RectangleBuilder<S, B>> extends ShapeBuilder<S, B>
    {
        private double height;
        private double width;

        @SuppressWarnings( "unchecked" )
        public B height ( double height )
        {
            this.height = height;
            return (B) this;
        }

        @SuppressWarnings( "unchecked" )
        public B width ( double width )
        {
            this.width = width;
            return (B) this;
        }
    }

    public static RectangleBuilder<?, ?> builder ()
    {
        return new DefaultRectangleBuilder();
    }

    protected Rectangle ( RectangleBuilder<?, ?> builder )
    {
        super( builder );
        this.height = builder.height;
        this.width = builder.width;
    }

    private static class DefaultRectangleBuilder extends RectangleBuilder<Rectangle, DefaultRectangleBuilder>
    {
        @Override
        public Rectangle build ()
        {
            return new Rectangle( this );
        }
    }
}

class RotatedRectangle extends Rectangle
{
    private final double theta;

    public double getTheta ()
    {
        return theta;
    }

    public static abstract class RotatedRectangleBuilder<S extends RotatedRectangle, B extends RotatedRectangleBuilder<S, B>> extends Rectangle.RectangleBuilder<S, B>
    {
        private double theta;

        @SuppressWarnings( "Unchecked" )
        public B theta ( double theta )
        {
            this.theta = theta;
            return (B) this;
        }
    }

    public static RotatedRectangleBuilder<?, ?> builder ()
    {
        return new DefaultRotatedRectangleBuilder();
    }

    protected RotatedRectangle ( RotatedRectangleBuilder<?, ?> builder )
    {
        super( builder );
        this.theta = builder.theta;
    }

    private static class DefaultRotatedRectangleBuilder extends RotatedRectangleBuilder<RotatedRectangle, DefaultRotatedRectangleBuilder>
    {
        @Override
        public RotatedRectangle build ()
        {
            return new RotatedRectangle( this );
        }
    }
}

class BuilderTest
{
    public static void main ( String[] args )
    {
        RotatedRectangle rotatedRectangle = RotatedRectangle.builder()
                .theta( Math.PI / 2 )
                .width( 640 )
                .height( 400 )
                .height( 400 )
                .opacity( 0.5d )
                .width( 111 )
                .opacity( 0.5d )
                .width( 222 )
                .height( 400 )
                .width( 640 )
                .width( 640 )
                .build();
        System.out.println( rotatedRectangle.getTheta() );
        System.out.println( rotatedRectangle.getWidth() );
        System.out.println( rotatedRectangle.getHeight() );
        System.out.println( rotatedRectangle.getOpacity() );
    }
}

Note though it requires some unchecked casts, and depends on the convention that each Builder’s generics are self-referential, which could present some risk if it’s extended further. Test early, test often.

The 21st century: ur doin it wrong

I know Oracle must be drilling way below the bottom of the barrel these days trying to find a way to monetize Larry Ellison’s vanity purchase get some return out of the Sun buyout, but hiding the JAX-RS specification under a bushel and charging US$50k to implement it commercially mostly strikes me as a great way to make sure no significant amount of RESTful web service development ever gets done in Java.

(Comments closed due to spam.)

Fun, or rather no fun, with generics (updated)

So, I understand that the following doesn’t work, but why doesn’t it work?

interface Adapter {}

class Adaptulator<I> {
    <e, A extends I & Adapter> void add(Class extl, Class<A> intl) {
    	addAdapterFactory(new AdapterFactory(extl, intl));
    }
}

The add() method gives me a compile error, “Cannot specify any additional bound Adapter when first bound is a type parameter” (in Eclipse), or “Type parameter cannot be followed by other bounds” (in IDEA), take your pick.

Clearly you’re just Not Allowed to use the type parameter I there, before the &, and that’s that. (And before you ask, it doesn’t work if you switch ’em, because there’s no guarantee that I isn’t a concrete class.) But why not? I’ve looked through Angelika Langer’s FAQ and can’t find an answer.

Generally when some generics limitation seems arbitrary, it’s because you’ve created a situation where the type system can’t actually enforce correctness. But I don’t see what case would break what I’m trying to do here. I’d say maybe it has something to do with method dispatch after type erasure, but there’s only one add() method, so it’s not like there’s any ambiguity…

So what’s the problem?


Update: I cross-posted this to stackoverflow.com, and Bruno De Fraine pointed out that the reason multiple bounds were introduced in the first place was to control the erasure. Since I is just going to erase to Object, it doesn’t get you anything there. Unfortunately, all I wanted was the compile-time type safety, so I’ll have to come up with something else…

Come on aboard, I promise you you won’t hurt the horse

Another bandwagon I haven’t jumped on is REST. It’s amazing how many words you can read about a technical subject without being sure if you’ve actually learned anything or not. But this Dare Obasanjo post made a bit more sense. Apparently in a REST architecture:

  1. interaction is client-server
  2. communication between client and server is stateless
  3. requests [is that a typo? does he mean responses?] are either cacheable, or not, and marked as such.
  4. [mumble mumble jargon genitives technobabble]
  5. there can be multiple layers (proxies, gateways, etc.) betwen client and server without the client or server having to know or care

That all seems pretty straightforward, and not really deserving of a sweet piece of technical nounage like representational state transfer. Perhaps the devil is in the one point that’s slightly less straightforward than the others? I’ll let you guess which one that is.

No, I’ll just quote Obasanjo directly.

there is a uniform interface between components which allows them to communicate in a standard way but may not be optimized for specific application scenarios. There are four interface constraints:

  1. identification of resources;
  2. manipulation of resources through representations;
  3. self-descriptive messages; and,
  4. hypermedia as the engine of application state.

(Emphasis in original; numbered points broken out by yours truly.)

Oh, and he was doing so well! It’s not obvious to me what any of these mean, and it’s even less obvious what they might mean as constraints. “Identification” is not a constraint. “…through representations” might be a constraint, but it seems like a pretty trivial one to me — how, in the client-server system described above, is a client going to manipulate (or even see) any resource except via some representation? I guess “self-descriptive” could be a constraint, though having worked with a number of people who claimed to write self-documenting code, it seems to me to be a constraint loose enough to drive a truck through. I might understand the last one if I knew what “hypermedia” and “engine” were supposed to mean in this context. Then again, I might not.

Luckily for us Obasanjo does go on to address these in more detail. As far as I can tell, they translate to:

  1. Give everything a URI.
  2. Use well-known data types / formats.
  3. Make proper use of protocol headers, error codes, etc.
  4. Use links, not cookies.

Okay, I think I’m starting to get it. (1)-(3) lay out a sort of minimalist program for web services, one I think I could get behind, in principle. It certainly sounds more fun than generating SOAP stubs and skeletons. (4) is the interesting one. On the one hand, it follows logically from “stateless” — or would, if all us Java EE developers weren’t used to thinking of state(less|ful)ness as a property of the server. On the other hand, true statelessness is hard for a procedural / OO programmer to wrap your head around.

“This is probably the most controversial and least understood of the architectural constraints set forth in Roy Fielding’s dissertation,” says Obasanjo, and I’m not surprised. Because, Fielding’s cogent arguments against cookies aside… the problem of representing state with URIs looks, to me, a whole lot like the problem of representing state with functions.

Comments closed due to spam.