Beautiful design is great, but more often than not it doesn’t pay the bills. Pragmatism is one of the things I try to instil in the SEED students. There’s a time for complicated diagrams with lots of lines and boxes, there’s a time to elegantly partition the layers, but there’s also a time to ‘it it wiv an ‘ammer.
I was recently dealing with some simple comms routing. I needed to store information about an address composed of 3 parts;
- Sector – a byte in storage this identifies the organisation to send the message to.
- Node – 10 bits denoting the unit to send to, e.g a Fire Station or Fire Appliance (Engine)
- Port – a byte indicating the application to send to, e.g. the printer
The information that I needed to associate with this address was actually an email address that it translated to. So it’s an obvious tree structure, right? What’s more you can use an XmlSerializer to save and load the relationships – this is important because it needs to be maintained by a human. So let’s look at the XML.
<Sectors> <Sector SectorNumber="0"> <Nodes> <Node NodeNumber="0"> <Ports> <Port PortNumber="0"> <EmailAddresse>postmaster@localhost</EmailAddress> </Port> </Ports> </Node> </Nodes> </Sector> </Sectors>
Oh dear, that’s not very good. There could easily be a couple of hundred entries which would mean that the sector you’re editing might not be in view, similarly the node. It’s all a bit untidy really.
Also lookup isn’t simple, you need to do it in 3 stages, find the Sector, then find the Node, then find the Port. So the XML is untidy and the code is untidy. But the design is good, yes?
There’s a better way. I realised that 8 bits + 10 bits + 8 bits is 26 bits and a standard csharp integer is 32 bits. So we can just have one object that has Sector, Node and Port properties but has a "lookup" value that is an int and is made up from the parts of the address.
[XmlElement] public class AddressRelation { [XmlAttribute] public byte Sector { get; set; } [XmlAttribute] public short Node { get; set; } [XmlAttribute] public byte Port { get; set; } [XmlAttribute] public string EmailAddress { get; set; } //so that we can get a lookup code, for comparison public static int GetLookupCode(byte Sector,short Node,byte Port) { return Sector << 24 | Node << 8 | Port; } public int GetLookupCode() { return AddressRelation.GetLookupCode(Sector, Node, Port); } //this makes serialization much easier public static AddressRelation CreateFromLookupCode(int LookupCode,string email) { return new AddressRelation() { Sector = (byte)(LookupCode >> 24), Node = (short)((LookupCode >> 8) | 0xffff), Port = (byte)(LookupCode | 0xff) }; } }
That’s great. When we call “GetLookupCode()” we get an int that uniquely represents that address. We can easily compare that with other objects.
Note that I didn’t override GetHashCode() or the equality operator because although the addresses might be the same, the emails may be different.
If you’re wondering what >> and << do then you need to look up bit shifting and bit masking…
They serialize nicely, too.
<AddressRelations> <AddressRelarion Sector="0" Node="0" Port="0" EmailAddress="postmaster@localhost0" /> <AddressRelarion Sector="0" Node="0" Port="1" EmailAddress="postmaster@localhost1" /> <AddressRelarion Sector="0" Node="0" Port="2" EmailAddress="postmaster@localhost2" /> </AddressRelations>
But how do we use it? Well actually the easiest thing to do is to load it into a dictionary, which should make the uses of some of the extra methods in the data class clearer…
[Serializable] [XmlRoot(Namespace = "www.tomfosdick.com/blogstuff", ElementName = "AddressStore", IsNullable = true)] public class AddressLookup2 { [XmlArray("AddressRelations")] public AddressRelation[] AddressRelations { get { return addressLookup .Select(x => AddressRelation.CreateFromLookupCode(x.Key, x.Value)) .ToArray(); } set { addressLookup = value .ToDictionary(k => k.GetLookupCode(), v => v.EmailAddress); } } private Dictionary<int, string> addressLookup = new Dictionary<int,string>(); public string GetAddress(byte sector, short node, byte port) { string result; if(addressLookup.TryGetValue(AddressRelation.GetLookupCode(sector, node, port),out result)) return result; return null; } }
The advantage of using a Dictionary here and of the way I’ve shuffled the parts of the address into an int means that this will always be serialized in order, Sector most significant then Node then Port. So editing the XML by human hand will be easy – even if one is added out of sequence when the file is next machine written it will be sorted again.
The code is neat, concise and fast. The design is a bit mucky, it’s not clean and elegant but in all other ways – some of which are far more important than the cleanliness of design – this wins.