Mon, Dec 22, 2003
Frank Hileman (no blog, Frank?) has been pounding us (the Avalon team) with some great
questions. Most of these have been conversations in comments, but I figured
that I should bring this to the front page, so to speak.
Of of Frank's concerns, at least as expressed in my comments section, is
that we are essentially over engineering with the Changeable pattern. Specifically,
he says this:
The non-intrusive part of Changeable has a const flag on stock brushes, etc. This
means the user must clone these and write to a copy. This is simple and easy to understand.
The intrusive part of Changeable is the part which introduces the idea of "used" and
changes the simple concept of "object reference" into something complicated. From
what you have explained, the intrusive part of Changeable exists only to allow simultaneous
multi-threaded writers to the scene graph.
I think that the point around threading is a red-herring here.
Changeable Requirements
The main problem that changeables are trying to solve is that we want to have, under
most circumstances, these changeable API objects (Brush, Pen, etc.) act like value
types and other times we want them to act like refernce types. We essentially
want to walk a fine line and get advantages from each. Frank, forgive me if
you've heard a bunch of this before, but I think that this is useful to put together
in one place.
With respect to value types, we want the simple user semantic to be: Modify my local
copy and have it be disconnected or "severed" when I pass it in to another object.
When rendering this means that I can do this:
SolidColorBrush scb = new SolidColorBrush();
scb.Color = Colors.Blue;
myDrawingContext.DrawGeometry(scb, null /*pen*/, myGeometry1);
scb.Color = Colors.Red;
myDrawingContext.DrawGeometry(scb, null /*pen*/, myGeometry2);
and have my two geometries be drawn with different colors. Since many times
the drawing context is writing in to a metafile like data structure, if we used pure
reference semantics both geometries would be drawn as Red.
Another example is setting properties in to a set of elements:
SolidColor Brush scb = new SolidColorBrush();
scb.Color = Colors.Blue;
myElement1.Background = scb;
myElement2.Background = scb;
// in some other code...
((SolidColorBrush)myElement1.Background).Color = Colors.Red;
I think, in most cases, if some user walks up to an element, such as myElement1 above,
and sets the background brush, he doesn't expect that this will have the side effect
of changing the background brush of another element. Side effects like this
would make for an unstable system.
Value types don't solve all of our problems though! We also want the efficiencies
and advanatages of making these be reference types. Specifically, we want to
have to have hierarchies, not have to copy the entire thing around and be able to
save space by having multiple places reference the same thing. We imagine a
world with lots of elements getting their values via styling. Because of this,
we want the normal case be that a user puts a value in to a style block somewhere
and then that object (via reference) gets widely shared, perhaps between hundreds
of elements. We obviously want to make the per-reference cost as small as possible
here. Going with a pure value type system won't work.
The Builder Pattern
The first cut at solving this problem was called the Builder Pattern. This pattern
was a generalization of the StringBuilder pattern. Specifically, for each type,
there was a companion, or Builder type. The main type was immutable. In
other words, any write operations would raise an exception. So, for SolidColorBrush,
we wrote a SolidColorBrushBuilder. SolidColorBrush was immutable while SolidColorBrushBuilder
was a fully writeable object. All of the APIs in the system (property types
on element, parameters in to DrawingContext methods, etc.) took the non-builder types.
To create one of those non-builder types, the user had to do one of the following:
-
Create the target type directly by using a constructor.
-
Create a builder, configure it, and call the ToXXX method on it to construct the main
type.
-
Create a builder by initializing it off of its companion type. Make a delta
and call the ToXXX method to construct the main type.
From one point of view, this is a great system. It is very explicit (at compile
time) what is editable and what isn't. However, some early users of our system
(ad-hoc usability) ended up really disliking this system. The need to constantly
deal with both types was onourous. It also meant that the user could never just
index directly in to a type and change it. To affect any change, it was
always necessary to create the builder, make the change, and set the old value in.
The Changeable Pattern
In response to a hail of internal criticism on the Builder Pattern we went back and
came up with the Changeable pattern. The idea is that we would have one type
object with an immutable bit. Once the immutable bit gets set, all further changes
to that object will throw. This opens the way up to runtime errors (instead
of compile time errors) but makes certain "delta" scenarios much easier to deal with.
However, we still wanted to solve the problems listed in the first part of this post.
Specifically, we didn't want to leave it up to the user to manually flip the immutable
bit. In this way we defined certain types of operations that would automatically
create a copy and flip the immutable bit for you if necessary. We then went
further and added advanced modes so that those APIs either make a copy but don't flip
the immutable bit or the APIs just take a reference. These last modes (whereby
something like an element holds on to a mutable brush) were never possible
with the Builder pattern.
We also added a notification mechanism so that someone who does hold on to an object
that isn't immutable can learn when that object changes.
So, do you guys (Frank?) like the more explicit builder pattern better? More
questions?