Part 4. Strongly typed associations
Introduction
Solution outline
Solution implementation
Summary
Introduction
In the previous parts (1, 2,
3) we have constructed a quite usable and functional
framework. However it still has some drawbacks. One of them is the necessity of
typecasting when accessing controllers, views and tasks. The example below demonstrates
such typecasting:
public class ProductsView : WebFormView, IProductsView
...
private void ShowProductDetailsButton_Click(object sender, EventArgs e)
{
(Controller as ProductsController).ShowProductDetails(); // typecasting required
}
As a system grows such typecasts may bloat code excessively decreasing its readability
and leading to errors. To eliminate this drawback we need a means of explicitly
specifying the type of the associated controller (or view/task). In other words
we need to make associations between tasks, controllers and views strongly typed.
Solution outline
The most obvious solution is to isolate the typecasing operation in a new property
of the required type:
public class ProductsView : WebFormView, IProductsView
...
private new ProductsController Controller
{
get { return base.Controller as ProductsController; }
set { base.Controller = value; }
}
private void ShowProductDetailsButton_Click(object sender, EventArgs e)
{
Controller.ShowProductDetails(); // typecasting NOT required
}
Although acceptable, this solution requires several additional lines of code. More
elegant solution can be constructed with a handy feature of .NET framework called
Generics. Generics mechanism allows varying a class members' types by specifying
those types in the class declaration. For example it is possible to adjust a return
type for certain properties by writing that type in brackets in the class definition
line.
Applying Generics we could strictly specify the type of the association between
a view and its controller as in the code below:
public class ProductsView : WebFormView<ProductsController>, IProductsView
...
private void ShowProductDetailsButton_Click(object sender, EventArgs e)
{
Controller.ShowProductDetails(); // typecasting NOT required
}
To make the above code workable we need to extend our framework, adding the generics
support.
Solution implementation
First of all we will add generic view and controller interfaces to the framework.
They will extend old IView and IController interfaces with new strongly typed
generic associations:
public interface IView<T> : IView where T : IController
{
new T Controller
{
get;
set;
}
}
public interface IController<TTask, TView> : IController where TTask : ITask
{
new TTask Task
{
get;
set;
}
new TView View
{
get;
set;
}
}
These interfaces alone do provide strongly typed associations, however we also need
to write some base generic implementation classes for these interfaces. So a developer
will only inherit these base classes instead of implementing the interfaces above.
We will implement the properties simply with backing fields and mark them virtual
so that a developer may override them in subclasses:
public class WinFormView<T> : Form, IView<T> where T : class, IController
{
...
protected T controller;
public virtual T Controller
{
get { return controller; }
set { controller = value; }
}
IController IView.Controller
{
get { return Controller; }
set { Controller = value as T; }
}
...
}
public class ControllerBase<TTask, TView> : IController<TTask, TView>
where TTask : class, ITask
where TView : class
{
protected TTask task;
protected TView view;
public virtual TTask Task
{
get { return task; }
set { task = value; }
}
public virtual TView View
{
get { return view; }
set { view = value; }
}
ITask IController.Task
{
get { return Task; }
set { Task = value as TTask; }
}
IView IController.View
{
get { return View as IView; }
set { View = value as TView; }
}
}
Note that the non-generic IView
and IController
interfaces
are implemented as gateways to the strongly typed generic properties. This makes
the access in the old non-generic manner (as done by the framework) equivalent to
accessing the new strongly typed properties.
Below is an example of using the new generic features of the framework:
class MyController : ControllerBase<MyTask, IMyView>
{
public void MyOperation()
{
View.MyViewOperation(); // Typecasting to IMyView NOT required
}
public override MyTask Task
{
get { return base.Task; }
set
{
base.Task = value;
// Controller initialization here
...
}
}
}
Summary
In
the article we have developed new framework features which make it more
usable and allow to avoid typecasting errors.