From Unit Test to (Slightly) Odd Design
"And you may find yourself in a beautiful house, with a beautiful wife,
And you may ask yourself - how did I get here?"
Talking Heads - Once in a Lifetime
Recently I needed to write a library that produces various iCalendar components such as VEvents and VTodos. The model is quite simple, each component can have a number of different properties and each property can have a number of parameters. There are different rules for how the properties can be used with the different components, but the schema is consistent for all components. For instance a VEvent cannot have both a DTEND and a DURATION property at the same time, but you can have zero or one CLASS or DESCRIPTION properties and any number of COMMENT properties. Similar rules apply to which parameters a property can have.
For the model as a whole there are few components and a large amount of properties and parameters.
As always; I wrote the unit tests first and refactored my way to an extensible design. The only thing that puzzles me is the design I ended up with.
The model is a perfect match for a OO-model. Below is a class diagram depicting some of the types in my final design.
At the bottom you can find the V* components, which inherit from the abstract Component class. Each Component has a collection of properties. The Property class is abstract, but you can see a feq concrete properties at the top. Each property has a collection of parameters. Again the Parameter class is abstract, and there are some concrete implementations at the right hand side of the model.
You cannot really see anything odd on this 10,000 foot view, but if we peek inside some of the classes things turned out rather strange.
[OneOrLess("DTSTART","GEO","LOCATION","ORGANIZER","STATUS","SUMMARY","TRANSP","URL","RECUR")]
[One("DTEND","DURATION")]
[ZeroOrMore("ATTENDEE","CONTACT")]
public class VEvent : Component
{
public VEvent(params Property[] properties) : base(properties)
{
}
}
The rules for which property usage is totally declarative and the rules are enforced in the Component base class. The same design is used for the properties.
[OneOrLess("ALTREP","LANGUAGE")]
[ZeroOrMore("XPARAM")]
public class Location : TextProperty
{
private Uri locationUri;
public Location(string location) : base(location)
{
}
public Location(string location, Uri locationUri) : base(location)
{
this.locationUri = locationUri;
}
public override string Value
{
get
{
if (locationUri != null)
{
return string.Format("\"{0}\":{1}", locationUri, base.Value);
}
else
{
return base.Value;
}
}
}
}
No matter how odd looking the design is, the semi-dynamic design pleasant to work with and new parameters, properties and components can be added in a matter of seconds. Still I have to ask myself; How did I get here?