Start with http://andersnoras.com/blogs/anoras/archive/2007/07/04/i-m-coming-down-with-a-serious-case-of-the-dsls.aspx and come back.
Just to continue the world's longest run on sentence. Before I start, here's the table of contents for the "Build your own CAB" series:
- Preamble
- The Humble Dialog Box
- Supervising Controller
- Passive View
- Presentation Model
- View to Presenter Communication
- Answering some questions
- What's the Model?
- Assigning Responsibilities in a Model View Presenter Architecture
- Domain Centric Validation with the Notification Pattern
- Unit Testing the UI with NUnitForms
- Event Aggregator
- Rein in runaway events with the "Latch"
- Embedded Controllers with a Dash of DSL (This Post)
- MicroControllers - Forthcoming
- Subcutaneous Testing - Forthcoming
- Creating the Application Shell - probably a couple posts
- Wiring the Components with an IoC tool - Forthcoming
Why is this post necessary?
Why, you might ask, are you writing a post on what amounts to a "ViewHelper?" One simple reason - View's can easily become absolutely massive blobs of code. Any chance to move a cohesive set of screen responsibilities into another class should serve to make the View itself simpler, and that's all this post is about. Plus, breaking a View's behavior into multiple, cohesive classes can potentially lead to reuse opportunities for the little Embedded Controller classes.
For much of the last three years I've worked with a lot of legacy code over a half-dozen different codebase's. All of them, to be charitable, were less than ideal in quality and structure. If you ask me what the single biggest flaw or problem across all of these codebase's my answer would be near automatic -- long classes and long methods. What I continuously see is code stuffed into modules until the modules are coming apart at the seams.
Come to think of it, my current project is about 95% greenfield code so far. Come Thanksgiving time when it's my turn to say what I'm thankful for my answer might just be "I'm thankful for getting six months of working on a greenfield project."
Embedded Controller
Again, I'm not a sanctioned patterns naming body, but the term "Embedded Controller" is my name for nonvisual classes that help a View control some distinct part of it's behavior. The first example that comes to my mind is from a WinForms project that used a 3rd party grid (not a vendor on the CodeBetter friends list by the way). The grid control needed a lot of consistent help and infrastructure code around it to implement the behavior we needed (little things like sorting and paging). We quickly discovered that additional screens needed the exact same bootstrapping code, so the obvious answer was to extract that "grid helper" into it's own reusable class. We were using a pretty strict Passive View approach, but even so, we didn't want the Presenter's tied that tightly to the screen mechanics. Instead, the new GridHelper class was just something that the View controls used internally. After the third screen with the grid control, development suddenly went faster.
To differentiate Embedded Controller classes somewhat from the Presenter, here's the attributes of an Embedded Controller:
- Nonvisual class used by a View to implement some of the View behavior. I guess in
- Completely encapsulated within a View. There is no sign of the Embedded Controller in a View's publicly facing interface.
- Embedded Controller's are happily aware of the actual, concrete UI widgets. The Embedded Controller class "knows" about buttons and checkbox's and the nasty 3rd party grid that you're being forced to use.
- Very limited in scope. An Embedded Controller provides classical controller functionality for a very specific part of the screen
My advice for taking advantage of Embedded Controller's is threefold:
- Look for common UI coding tasks within a system and look for opportunities to encapsulate some screen mechanics in reusable pieces. This is just another exercise in eliminating duplication.
- Split up any View class that gets too big. I might have left the impression in earlier posts that the View code is somehow exempt from the normal coding standards because we've made it "simpler" now. Code quality matters everywhere, and especially in areas of the code that are likely to change over time -- like View's. Even with a Passive View architecture a complex screen will almost inevitably lead to complex code in the View.
- Pulling out an Embedded Controller might be an easy way to extend unit testing deeper into the View. This won't always be true, but a "POCO" embedded controller class can often be quite easy to unit test inside vanilla xUnit tests without resorting to anything exotic like NUnitForms. There is some widget behavior that only functions when a Form is visibly displayed, but a lot of behavior can be tested just by instantiating UI widgets directly within a unit test. One way or another the UI widget stuff is nothing but CLR classes.
Sample: The Control State Machine
Here's a scenario from my current project that I bet all of us have dealt with a few times over. We have a Trade screen that has seven different states depending upon whether you're creating or reviewing a Trade. The various user actions available on the screen differ from state to state. As the screen changes state either upon opening the screen or a result of user actions while it's open we need to enable/disable and show/hide different screen elements. The screen started simple, so I just coded specific methods at first to enable or disable bits of the screen. Fast forward a couple of weeks and the behavioral logic had exploded (funny how that happens when you actually get to talk to the end users). Unsurprisingly, the code in the Presenter and View had become hairy, plus the screen had way too much flicker for that matter.
Before I show any code, let's be pretty clear that this code is not very optimized or even very powerful. All I want to talk about is the concepts and structure of the design irrespective of performance.
At least in concept, the solution was pretty simple. Move that logic into a state machine construct. Since we already had a full set of regression tests against the UI screen itself, I felt pretty safe making the changes. All I did was create a class called "ControlState" that's nothing but a collection of Control's to display and enable for a particular screen state. ControlState has a method called Activate() which simply loops through its internal collection to enable and show the configured controls (it's not shown but the call to Activate() is wrapped in SuspendLayout() and ResumeLayout()).
public class ControlState
{ private List<Control> _displayedItems = new List<Control>();
private List<Control> _enabledItems = new List<Control>();
public void ShowControls(params Control[] controls)
{ _displayedItems.AddRange(controls);
}
public void EnableControls(params Control[] controls)
{ _enabledItems.AddRange(controls);
}
public void Activate(ControlStateMachine<T> machine)
{ machine.LevelSet();
foreach (Control item in _enabledItems)
{ item.Enabled = true;
}
foreach (Control item in _displayedItems)
{ item.Visible = true;
}
}
}
As you can probably guess, there's a second class that aggregates all of the configured ControlState's and Control's called ControlStateMachine<T>, where T is an enumeration of the possible states. Here's a little bit of its implementation.
private readonly Control _parent;
private readonly IScreenBinder _binder;
private List<Control> _displayedItems = new List<Control>();
private List<Control> _enabledItems = new List<Control>();
private Dictionary<T, ControlState> _states = new Dictionary<T, ControlState>();
private T _currentState;
public ControlStateMachine(Control parent, IScreenBinder binder)
{ _parent = parent;
_binder = binder;
}
public void SetState(T stateKey)
{ _parent.SuspendLayout();
_states[stateKey].Activate(_binder, this);
_currentState = stateKey;
_parent.ResumeLayout();
}
The View itself just calls ControlStateMachine.SetState() to configure itself.
Now, for the cool part. Here's a somewhat obfuscated version of our code that defines the state machine inside the View.*
private void configureStateMachine()
{ _stateMachine = new ControlStateMachine<TradeDetailState>(this, _binder);
_stateMachine.OnStateChangeTo(TradeDetailState.Creation)
.Show(createTradeButtonsPanel)
.Enable(status1CheckBox, status2CheckBox, status3CheckBox, externalTradeIdTextbox);
_stateMachine.OnStateChangeTo(TradeDetailState.Review)
.Show(updateTradeButtonsPanel)
.IsReadOnly()
.Enable(
editTradeButton,
status1CheckBox,
status2CheckBox,
status3CheckBox,
externalTradeIdTextbox,
cancelTradeButton);
_stateMachine.OnStateChangeTo(TradeDetailState.ReviewDirty)
.Show(updateTradeButtonsPanel)
.IsReadOnly()
.Enable(
undoButton,
status1CheckBox,
status2CheckBox,
status3CheckBox,
submitTradeChangesButton,
externalTradeIdTextbox,
cancelTradeButton);
_stateMachine.OnStateChangeTo(TradeDetailState.Edit)
.Show(updateTradeButtonsPanel)
.Enable(externalTradeIdTextbox, cancelTradeButton);
_stateMachine.OnStateChangeTo(TradeDetailState.EditDirty)
.Show(updateTradeButtonsPanel)
.Enable(undoButton, submitTradeChangesButton, externalTradeIdTextbox, cancelTradeButton);
_stateMachine.OnStateChangeTo(TradeDetailState.Cancelled)
.Show(updateTradeButtonsPanel)
.DisableEverything()
.IsReadOnly();
_stateMachine.OnStateChangeTo(TradeDetailState.History)
.Show()
.DisableEverything()
.IsReadOnly();
}
If you haven't seen this kind of syntax before, it's what Martin Fowler (a real patterns naming authority) has christened Fluent Interface. Why, oh why, did I go to the extra trouble of making a Fluent Interface instead of just defining the state machine by filling up the collection state (and let's be clear, it is a little more work)? Because I thought it would make a good blog post wanted to create Domain Specific Language in the code to make the code easier to understand.
That's Not Really a DSL!
Unfortunately, in my opinion, the .Net community is fixated on graphical Domain Specific Language's (DSL) that revolve around yet more code generating visual tooling. There's a complete other side to the DSL's however. Another alternative is lexical languages which could be either internal/embedded or external to the language, with the pro-lexical argument being something like "people can read English you know." I'm not particularly enamoured of creating my own programming language and interpreter, so my particular area of interest right now is in creating embedded DSL-like syntax's inside existing languages. Granted, C# is very limited in th