Fluent hierarchical construction
Building a hierarchy of objects doesn’t happen that often in code, but when it does, it can get pretty ugly. Most of the time, the hierarchy will come out of the database. Recently, we had a hierarchy that needed to be built straight in code. We had something like this going on:
public static Menu BuildMenu() { var menu = new Menu(); // Main menu var products = new MenuItem(); products.Title = "Products"; var services = new MenuItem(); services.Title = "Services"; var support = new MenuItem(); support.Title = "Support"; var contact = new MenuItem(); contact.Title = "Contact"; menu.AddItem(products); menu.AddItem(services); menu.AddItem(support); menu.AddItem(contact); // Products menu var hardware = new MenuItem(); hardware.Title = "Hardware"; var software = new MenuItem(); software.Title = "Software"; products.AddItem(hardware); products.AddItem(software); // Hardware menu var computers = new MenuItem(); computers.Title = "Computers"; var printers = new MenuItem(); printers.Title = "Printers"; hardware.AddItem(computers); hardware.AddItem(printers); // Services menu var consulting = new MenuItem(); consulting.Title = "Consulting"; var onSiteSupport = new MenuItem(); onSiteSupport.Title = "On-Site Support"; services.AddItem(consulting); services.AddItem(onSiteSupport); return menu; }
Our example was different, but it’s the same idea of building a hierarchy of objects. In the above code, we’re trying to build out our menu structure for a website. This is a simple hierarchy, but more complex ones could go on for hundreds of lines. It’s almost impossible to see what’s going on there, and what is a child of what. You can try and create conventions inside the code, such as the comments and organization of the building.
There are some more fluent ways to build hierarchies out there, as shown by LINQ to XML. Instead of manually setting up all of the objects, we use one statement to construct everything, making use of existing features around since .NET 1.0.
The basic idea behind fluent hierarchical construction is:
- Create a constructor that takes all necessary properties to set up the individual item
- Add a params parameter that takes all the children for the parent
First, we can switch our Menu class to use fluent construction:
public class Menu { private readonly List<MenuItem> _menuItems = new List<MenuItem>(); public Menu(params MenuItem[] menuItems) { _menuItems.AddRange(menuItems); } public void AddItem(MenuItem menuItem) { _menuItems.Add(menuItem); } public IEnumerable<MenuItem> GetMenuItems() { return _menuItems; } }
We created a constructor that took a param array of child menu items, which are then added to its menu items collection. Next, we need to switch the child type to take both the Title as well as the child menu items:
public class MenuItem { private readonly List<MenuItem> _menuItems = new List<MenuItem>(); public MenuItem(string title, params MenuItem[] menuItems) { Title = title; _menuItems.AddRange(menuItems); } public string Title { get; set; } public void AddItem(MenuItem menuItem) { _menuItems.Add(menuItem); } public IEnumerable<MenuItem> GetMenuItems() { return _menuItems; } }
All properties that we need to set on a hierarchy class need to go in the constructor for this technique to work. Optional or additional parameters can be added with overloaded constructors, with the additional parameters added just before the params one.
With our new constructors in place, our menu construction becomes quite a bit terser:
public static Menu BuildMenu() { var menu = new Menu( new MenuItem("Products", new MenuItem("Hardware", new MenuItem("Computers"), new MenuItem("Printers")), new MenuItem("Software")), new MenuItem("Services", new MenuItem("Consulting"), new MenuItem("On-Site Support")), new MenuItem("Support"), new MenuItem("Contact") ); return menu; }
Not only is the number of lines of code reduced, but I can far more easily discern the structure of the hierarchy just by looking at the structure of the construction. You can literally see the tree of objects in the construction. No more temporary variables hang around, just so we can add children with individual method calls. Because we used the params keyword, we don’t even need to provide any children if they don’t exist. Params also allow us to create a comma-separated list of the children, instead of creating an array.
The nice thing is, all this technique used was application of the params keyword in the constructor for child objects. As long as we ensure that all properties needed to be set are available in an overloaded constructor, this technique should work.