Recently I looked at MVC and as an aside I put together a small framework to handle MVC. It was originally just a case in point, but was so useful in simplifying my code that I've popped in into a library and now reused it in my latest project.
However I've just had an interesting experience as I've tried to convert a classic prototypical WinForms application, (think one class, lots of code in event handlers) and turn it into something more maintainable. First however I need to explain
MVC mark 3
Originally I wanted to develop this as a PassiveView, but when I got to the point of wanted to develop ControllerAdapters for each View, I decided to change to something more akin to SupervisingController. This had a very positive impact on the code.
First some rules, or at least some strong types
1. What is a model? Absolutely anything
2. What is a View? Well it's something that wants to be notified when a Model changes, or wants to make a change to the model.
public interface IView<M> :INotifyPropertyChanged
{ IEnumerable<string> PropertiesViewed { get; }
void UpdateViewProperty(string propertyName, M model);
void UpdateModelProperty(string propertyName, M model);
}
3. So a controller must be
public abstract class Controller<M>
where M : INotifyPropertyChanged
{ public Controller(M model){} private M _model;
protected M Model;
private Observers<IView<M>> _observers = new Observers<IView<M>>();
private void RegisterView(IView<M> view, string property)
public void RegisterView(IView<M> view)
public void UnregisterView(IView<M> view)
protected void View_PropertyChanged(object sender, PropertyChangedEventArgs e)
protected void Model_PropertyChanged(object sender, PropertyChangedEventArgs e)
private void UpdateObservers(string propertyName)
private void UpdateModelFromView(IView<M> view, string propertyName)
private void UpdateViewFromModel(IView<M> view, string propertyName)
}
What we then need to implement
Just enough to link it all together. When a view changes, the model gets updated, and all the views interested in the property that changed on the model get an update too.
I've implemented by Views as adapters between control and controller, and with lots of functionality in the hierarchy. As a result, a specific view type needs little code, e.g. this views maps a read-only label to its model.
class LastReadView : SingleControlSinglePropertyView<DownloaderModel, Label>
{ public LastReadView(GlassLabel control)
: base("LastReadDate", control) { }
public override void UpdateViewProperty(string propertyName, DownloaderModel model)
{ Control.Text = model.LastReadDate.ToString();
}
public override void UpdateModelProperty(string propertyName, DownloaderModel model)
{ throw new NotImplementedException();
}
}
It really is that simple.
Models and data longevity
The complexity comes when we look at our models. For my example, consider an application that maintains a list of URLs. That is our Data model.
However we also will be refreshing and analysing the resources attached to the other end of those URLs. That is a Progress model. Our list of URLs we would want to store between runs of our application. Our current state is transient data, we have no need to store it.
The question now is how to implement this, do we make the Progress state properties of the Data controller?
Or is it instead that the processing state is stored externally from the data controller, making the Data Controller a view on the Progress Model?
P.S. There are bonus points for anybody who can read my handwriting.
At this point you might be asking why bother?
In a single threaded environment you are right, YAGNI, however we end up back with a single object and a blocking UI.
As the number of threads increases then so does the complexity and need for synchronization. Our advantages come from,
- the tight cohesion between model and controller, making it easier to develop efficient and even lock free synchronisation strategies, that views can easily participate in without needing to be tightly coupled themselves.
- and the granularity of each synchronisation strategy, since each model can easily implement its own locking.
And if all this seems a bit much just to download some URLs, don't forget it can be applied to any long running operation, including financial calculations.