Friday, January 25, 2008

Extending MVP: Roles and Responsibilities

The system starts with Model classes. Model classes contain data, and all business rules which govern the handling of the data. No business rules should exist outside of model classes, and models should in no way be coupled to UI elements or infrastructure. Models must have well-defined public interfaces. Models communicate with infrastructure to manage things like persistence, printing, or other hardware interaction, but they do so through well defined interfaces that provide separation so that the models can be unit tested. Models may call methods on infrastructure classes, but may only communicate upwards (to the UI) through events.

Connecting models to the UI are presenter classes. Presenters contain no logic, and serve only as the connection between the model and the view interface. Presenters are passed a model and a view interface and can either hook up events directly, or respond to events themselves by calling methods. I prefer the latter solution as it allows models and views to be completely de-coupled, whereas with the former they must at minimum share delegate signatures. Presenters always have exactly one view and one model. Presenter handle events fired by models and views, and call methods on models and views.

View interfaces should contain no direct references to actual GUI widgets. Views should contain methods like ShowPrompt, and events like QuantityEntered, rather than exposing labelPrompt.Show or textBox.Text directly. Views can be instantiated by winforms, webforms, or any other UI technology.


Next I will discuss Workflow manager and transaction manager classes.

Wednesday, January 23, 2008

Extending MVP: Introduction

I've been working on a system that contains many relatively complex user interactions; interactions which require the user to navigate over several forms, entering information and interacting with the system at each one. As it is currently written, the system uses mainly Win32 dialogs, with data shared between dialogs in global variables. A mess. In re-designing the system, we wanted to use Model driven design, and an MVP like pattern for the UI components. Our initial design had model and form classes put into a repository and retrieved by the presenter, which worked ok, but it was too easy to forget to put a component in the repository. Forgetting to put a component in the repository led to runtime errors which could be challenging to track down. Also, with all the forms chained together, the presenter classes wound up being fairly tightly coupled. I wanted a system that made the construction mistakes impossible, and that allowed for de-coupled presenters.



The first challenge was figuring out how the de-coupled presenters would be chained together. The solution I came up with was a super-presenter, which I call the WorkflowManager. Initially, there was a lot of overlap between the presenters and the workflow manager. In fact, it was not at all clear where the divisions should be between the workflow manager and the presenter, or between the presenter and the form. Similarly, we have user workflows that involve multiple selections which the system must iterate over. Here the solution is a special class of model which contains a list of single transaction classes (each of which is itself a model) and the knowledge of how to iterate over the list. By layering the architecture we have been able to keep the classes relatively small and highly cohesive. The tests are all so simple they hardly seem worth doing, but in aggregate they do an excellent job of validating system behavior.

Tuesday, January 22, 2008

Cross threading in .NET

Working in a nice, modular, testable system sometimes presents new challenges. One of these is a timer in a class that begins a cascade of actions ending in updates to a form or control. The problem is the timer event is fired on a new thread, not the UI thread. You are not allowed to access UI components (anything derived from System.Control) from a thread other than the thread that created the component. This is a Windows issue, not a .NET issue, and it simplifies GUI programming. Fortunately the framework provides a way to get back to the UI thread. Every control has an Invoke method which takes a delegate as it's parameter, and an InvokeRequired property to tell you if you're on a thread other than the creator thread. So:


public delegate void SetTextDelegate(string text);

public void SetText(string text)
{
myTextBox.Text = text;
}

public void SetTextFromAnywhere(string text)
{
if (myTextBox.InvokeRequired)
{
myTextBox.Invoke(new SetTextDelegate(SetText), text);
}
else
{
SetText(text);
}
}


Keep in mind that the thread that calls Invoke will wait for the delegate to return. This can be the cause of some insidious deadlock and threadpool starving problems. A more robust multi-threading pattern is to use BeginInvoke, which schedules the delegate to executed, rather than executing it immediately. For you old C programmers, it's the difference between SendMessage and PostMessage. Also note that Invoke returns an object, which is whatever the delegate returns. With BeginInvoke you can use a delegate with a return value, but you must call EndInvoke to retrieve it.

Testability

Testability for me means unit testing. The ability to put a class in a test harness, simulate all of its possible inputs, and validate its outputs. To achieve this, the class must not be coupled to other classes. Coupling, in the sense that I mean it, would require you to test multiple classes simultaneously, rather than just the one. Often this leads to dependencies on things that can not be easily simulated in a test harness, like databases and hardware.

Testability also means simple tests. For tests to be simple, the class should have as few associations as possible. By associations I mean other classes with which the class under test must interact. Ideally these associated classes are substituted by mock or fake classes in the test harness. Each added association greatly increased test complexity.

For the tests to be simple the class itself should be simple. It may have highly complex logic, but it should be cohesive. That is, it should have a very limited set of responsibilities. If a class has one thing that it does, the programmer can readily ensure that the class does its one thing well. Cohesion simplifies testing because all the tests closely relate to one another. Closely related tests present opportunities for reuse in their setup and are thus easier to write.

All classes must have associations, however limited. Whenever possible these associations should be in the form of interfaces. Interfaces provide tremendous flexibility in design, and nowhere more so than in testing. Interfaces create the seams where test classes can be inserted in place of real ones, so that only the one class need be tested. One can readily instantiate the interfaces with fake or mock objects under test.

Of course testing classes with many associations is possible. It is now even possible to use aspect oriented programming techniques and frameworks like Typemock to test classes with all kinds of dependencies. I believe that designing for more traditional leads to better, more flexible design in general. In the ideal case software is designed for testability from the beginning. The next best case is refactoring existing code so that it can be tested. The least desirable scenario is using whatever means necessary to test existing code without changing it. In software, change is good. No software problem is ever solved optimally the first time. Software problems are rarely ever solved optimally.