Part 5 has been a long time coming. Originally, the plan was to present an entire UI in this post, which I've come to realize is simply not feasible.
I have been attempting to learn WPF as part of this exercise. Learning an entirely new UI framework while putting together this piece has proven a little bit difficult, and as a result the progress of the application has slowed significantly. So for the rest of these pieces, I'll try to break down the UI further to show the choices I make while it's being developed and why.
Today, we're going to take a look at the architecture I plan to use for the WPF sudoku game and why. We're going to take a look at my simple implementation of MVP (Model-View-Presenter). We're going to take a look at the advantages of a MVP architecture and how that fits with our goal of utilizing Behavior Driven Development to build our application.
Architecture Description
In Part 1 (Defining The Solver Behavior) I mentioned that we would be using Castle Windsor in this project. One of the great things about Windsor, and really any good inversion of control framework, is that you can minimize the invasion of container logic in your code via nested dependency injection. What I mean by this, is that you can use the container to resolve a single service and it will automatically populate all dependencies for all children. From our perspective, this will mean that the container logic can be restricted to just the UI, resulting in one less dependency throughout the application.
From previous parts we never made explicit use of Windsor, but rather we defined our inner dependencies via our available constructors. For example, our RecursiveGenerator that we built in Part 4 took an ISolver in the constructor which will instruct Windsor to automatically populate the appropriate implementation for our generator. This process is known as Constructor Injection.
I mentioned that we'll be using a Model-View-Presenter based architecture for our Sudoku game. The Model-View-Presenter obviously breaks down to three pieces. There are many variations of the MVP pattern (See Jeremy Miller's Build your own CAB series for some awesome overviews of various techniques), but the way we will use it for our purposes can be described as follows:
- View - The portion of the application that actually displays data to the user as well as listens for user input. Such as watching for button presses or mouse movement. For this application we will be using WPF for the view.
- Presenter - Handles the UI behavior logic. For example the view will show the data to the user, but the presenter will determine what data should be shown to the user and how it should be formatted. Also, while the view listens to see if a mouse moves, or a button is clicked, how that action is handled belongs to the presenter. So it would be possible for our game to have many "New Game" buttons on the view, but it would defer the appropriate actions to the presenter to determine what should happen when any of those buttons are pressed.
- Model - Handles the data which the presenter operates on. This would typically represent the description of the business domain which is being solved by our application. In our case this will represent the data needed to track the game which is being played, such as the puzzle which is being worked on, the current status of the solution which the user is building as well as supporting information such as the game clock or at least the time when the game was started.
So, why go through all of this? Especially for an application which seems as simple as a sudoku game? Well, first of all, we are doing this as an exercise, not necessarily the exact application. Secondly, and far more importantly, this separation of concerns allows us to keep logic more closely aligned to a purpose, and allows us to have fully testable behavior. By moving our presentation behavior from the XAML "code-behind" to a separate presenter object, we can test our presentation without the need for an actual UI. This is a major advantage over the old fashioned applications. We can now have a lot more faith that our application behaves as expected via our automated testing.
Additionally, MVP does not force us to be tied to the WPF view. It would be possible for us to re-use the same model and presenter in a WinForms application (or potentially silverlight and other web-based frameworks) with no change, only a different view. This will be considered out of scope for the time being.
WPF Implementation
First off, I want to make a disclaimer. I am not a WPF expert. If there is a better way to implement this in WPF, I would love to hear about it. I'm learning WPF as I go with this sample application.
My first idea was to create a base class which would initialize the appropriate presenter for that view. I quickly ran into a bit of a problem with WPF. It's valid to create your own base class instead of Window, but it adds a lot of complexity to the XAML, and Visual Studio didn't seem to appreciate it too much.
Since I only needed the base class to resolve the appropriate presenter via my container, I figured it would be ok to just have the convention of every view calling a static Initialize method. This keeps the XAML simple, and only adds one extra line of code to all views.
Application Implementation
I chose to initialize Windsor in the Application class, which is where the static Initialize also lives. The Application class looks like the following:
public partial class App : Application, IContainerAccessor
{
public static IWindsorContainer Container { get; private set; }
IWindsorContainer IContainerAccessor.Container
{
get
{
return Container;
}
}
protected override void OnStartup(StartupEventArgs e)
{
InitializeContainer();
base.OnStartup(e);
}
private void InitializeContainer()
{
Container = new WindsorContainer(new XmlInterpreter());
}
public static void InitializePresenter<T>(T view)
{
Presenter<T> presenter = Container.Resolve<Presenter<T>>();
presenter.Wireup(view);
}
}
Note that this is as far as our container needs to go into our sudoku application. When the application first starts we need to initialize our container based on the application configuration file. The InitializePresenter method is what we expect every view to call when the view is initialized. The type paramter (T) is the interface type which the view itself implements. That is the view that the appropriate presenter will use in order to interact with the view. The Presenter<T> will server as the abstract base class for all presenters. So in this case we're asking Windsor to locate and instantiate the appropriate presentation logic for our particular view. Once the presenter is located we call the base class method Wireup which tells the presenter to begin observing any events exposed by the view. This would also be the time that the presenter could perform any initialization logic. The Presenter base class looks like the following:
public abstract class Presenter<T>
{
public T View { get; private set; }
public void Wireup(T view)
{
View = view;
Initialize();
}
protected abstract void Initialize();
}
The View implementation would then contain the following:
protected override void OnInitialized(EventArgs e)
{
base.OnInitialized(e);
App.InitializePresenter<IBoardView>(this);
}
Where obviously in this case the view implements the IBoardView interface. Our appropriate presenter implementation would be defined as:
public class BoardPresenter : Presenter<IBoardView>
To wrap up our initial implementation we would need to configure our Windsor Container in our configuration file as follows:
<configuration>
<configSections>
<section name="castle"
type="Castle.Windsor.Configuration.AppDomain.CastleSectionHandler, Castle.Windsor" />
</configSections>
<castle>
<components>
<component
id="generator"
service="Sudoku.Bll.Generator.IGenerator, Sudoku.Bll"
type="Sudoku.Bll.Generator.RecursiveGenerator, Sudoku.Bll" />
<component
id="solver"
service="Sudoku.Bll.Solver.ISolver, Sudoku.Bll"
type="Sudoku.Bll.Solver.RecursiveSolver, Sudoku.Bll" />
<component
id="boardpresenter"
lifestyle="transient"
service="Sudoku.UI.Presenter`1[[Sudoku.UI.Views.IBoardView, Sudoku.UI]], Sudoku.UI"
type="Sudoku.UI.Presenters.BoardPresenter, Sudoku.UI" />
</components>
</castle>
</configuration>
In this case our presenter would have an IGenerator parameter in its constructor which would tell Windsor to construct the defined generator, which we've already seen requires a solver implementation. Therefore these three specific components are necessary to fully configure our Windsor container for this example. One item to note is that we have specified a lifestyle of transient for our presenter. This is to signify that every presenter should have its own containing class (since the state of the associated model will matter), whereas our generator and solver services have no defined state and can safely be use the default lifestyle of singleton.
At this point we have the basics defined for a Model-View-Presenter application in WPF. We are using Castle Windsor to manage our dependencies. Our View has no knowledge of our presenter implementation, and the presenter has no knowledge of our view implementation. All aspects are configurable and easily changeable. Plus, since our presenter does not know our view implementation it has opened the door to potential porting to an alternative UI framework.
In the next iteration I hope to examine the needed behavior of our presenter and potentially the implementations which satisfy that behavior.
--John Chapman
No comments:
Post a Comment