This example illustrates the design and implementation of push-based flow systems via an assembly line applet that builds series of ``paintings'' in a style vaguely reminiscent of Piet Mondrian and Mark Rothko.
Box, where every
Boxhas a color and a size, can display itself when asked, and can be made to deeply clone (
duplicate) itself. The color mechanics are default-implemented. Others are left abstract, defined differently in different subclasses:
The overall theme of this example is to start off with sources that
produce simple basic boxes, and then push them through stages that
paint, join, flip, and embed them to form the
BasicBoxes are the raw material:
Two fancier kinds of boxes can be made by joining side-by-side two
existing boxes, adding a line-based border surrounding them. Joined
boxes can also flip themselves. All this can be done in either of two
ways, horizontally or vertically. The two resulting classes can be
made subclasses of
JoinedPair to allow sharing of some
JoinedPair.java HorizontallyJoinedPair.java VerticallyJoinedPair.java
The final kind of fancy box wraps one
Box within a border:
BasicBoxSource: Create a new
Painter: Change the color of any kind of
Flipper: Flip (up-down or left-right) a
HorizontalJoiner: Combine two
Boxesleft-right, creating a new composite.
VerticalJoiner: Combine two
Boxesup-down, creating a new composite.
Wrapper: Wrap a
Boxinside a border, creating a new composite.
Cloner: Make a copy of a
Box; pass the original to one successor, and the clone to another.
Alternator: Pass alternate inputs through to alternate successors.
Screener: Pass some kinds of
Boxesto one stage, and others to another.
Collector: Direct the results of two independent assembly lines to a single successor.
Flippers) change the states of their sources using methods supported by the represented objects themselves, and then pass them on to other stages. Others accept zero (
BasicBoxSource), one (
Wrapper) or two (
Joiners) incoming objects to create new kinds of Boxes. Both
Alternatorsare kinds of Splitters.
Collectorsand related stages come into play as utilities to help with some of the plumbing.
Since we are doing push-based flow, these interfaces mainly describe
put-style methods. In fact, we could just call them all
put, except that this doesn't work very well for Combiner
stages. For example, a
VerticalJoiner needs two put
methods, one supplying the top
Box, and one for the
Box. We could evade this by designing
Joiners to take alternate incoming Boxes as the tops and
bottoms, but this would make them harder to control. Instead, we'll
use the somewhat ugly but easily extensible names
putB, and so on:
We can make the ``B'' channels of
completely transparent to other stages by defining a simple Adapter
class that accepts a
putA but relays it to the intended
putB. This way, most stages can be built to
putA, without knowing or caring that it is being
fed into some successor's B channel:
And, while we are focused on interfaces and adapters, here is a
Runnable Adapter that helps perform any
putA in a new
More interesting sinks require more interesting code. For example, in the applet used to produce the image shown at the beginning of this section, the
Applet subclass itself was defined to
PushStage. It served as the ultimate sink by displaying the assembled objects.
DevNull, each stage has at least one successor. There are several implementation options, including:
Now we can build all sorts of useful classes that extend either of these base classes, simultaneously implementing any of the standard interfaces.
Wrapper stages apply to
any kind of
Flippers only make
JoinedPairs. If a
something other than a
JoinedPair, it will just pass it
through. In a more ``strongly typed'' version, we might instead choose
to drop boxes other than
JoinedPairs by sending them to
Collector, that accepts messages on either channel, and relays them on to a single successor:
We have two kinds of Combiners, horizontal and vertical
Joiners. As did the representation classes, the stage
classes share enough in common to build a common superclass. Joiner
stages block further inputs until they are able to combine one item
putB. This can be
implemented via the usual guard mechanics.
Joiner.java HorizontalJoiner.java VerticalJoiner.java
Alternators output alternate inputs on alternate channels.
Cloners multicast the same element to both successors:
Screener is a stage that directs all inputs obeying
some predicate to one channel, and all others to the other. We can
build a generic one by encapsulating the
check in an interface, and implementing it for example with a class
that makes sure that a
Box fits within a given
(symmetric, in this case) bound. The
BoxPredicate and uses it to direct outputs:
BoxPredicate.java MaxSizePredicate.java Screener.java
Here is a sample source, one that produces
random sizes. For convenience, it is also equipped with an autonomous
loop repeatedly invoking
start, interspersed with random
Here's a fragment of the flow used in the applet that produced the image displayed at the beginning of this section.