Friday 31 October 2008

MVC questions

Having done all of the below, I am still left with a list of questions

  1. Should I be using threads to do this ?
  2. When the command requires interaction (i.e. if I want a form to search of a client or a stock item), should it be owned by the Model, Controller or View ?
To answer 2., the view owns the form, and and is updated by the model in the same way as before.

MVC in Delphi (Part III)

As an experiment I have changed the DomainModel of my example, to be a more complex object, encapsulating an 'order'. This can be updated, calculated and reset with out the View and the Controller having to change.

The DomainModel now looks like this ..


TDomainObject = class
private
FClient : String;
FStock : String;
FBuySell : String;
FSize : extended;
FPrice : extended;
FDate : String;

FConsideration : extended;
public
property Client : String read FCLient write FClient;
property Stock : String read FStock write FStock;
property BuySell : String read FBuySell write FBuySell;
property Size : extended read FSize write FSize;
property Price : extended read FPrice write FPrice;
property Date : String read FDate write FDate;
property Consideration : extended read FConsideration write FConsideration;
end;

///A small implementation of the MVC architecture
TDomainModel = class (TObservable)
private
/// Underlying data object
FData : TDomainObject;

public
///Property that represents the underlying data
property Data : TDomainObject read FData write FData;

///Executes the given command
procedure DoCommand (cmd : TCommand); override;

///Creates the underlying data
constructor Create;

///Function that is passed to the CommandThread for execution
///TCommandThread
procedure ExecuteCommand (cmd : TCommand);

function ToString : String;
end;


In order to do this, some of the plumbing code had to change, but basically just to get the values in the views. I have define 3 views, 2 of which are associated with one form, and a more complex one that is associated with another form, as below;


TFormView = class (TInterfacedObject, IObserver)
private
Ffrm : TForm2;
public
{ Public declarations }
procedure UpdateView (obj : TObject);
constructor Create (frm : TForm2);
end;

TListView = class (TInterfacedObject, IObserver)
private
Fctrl : TListBox;
public
{ Public declarations }
procedure UpdateView (obj : TObject);
constructor Create (ctrl : TListBox);
end;

TTextView = class (TInterfacedObject, IObserver)
private
Fctrl : TStaticText;
public
{ Public declarations }
procedure UpdateView (obj : TObject);
constructor Create (ctrl : TStaticText);
end;


These are created and associated with the Model and Controller as follows;


procedure TForm1.FormCreate(Sender: TObject);
begin
Controller.AddView (TListView.Create (ListBox1));
Controller.AddView (TTextView.Create (StaticText1));
end;

...

procedure TTextView.UpdateView (obj : TObject);
begin
Fctrl.Caption := FloatToStr((obj as TDomainModel).Data.Size);
end;

procedure TListView.UpdateView (obj : TObject);
begin
Fctrl.AddItem('Order = ' + (obj as TDomainModel).ToString, nil);
end;

...

procedure TForm2.FormCreate(Sender: TObject);
begin
Controller.AddView (TFormView.Create(Form2));
end;

...

procedure TFormView.UpdateView (obj : TObject);
begin
// Fctrl.AddItem('Order = ' + (obj as TDomainModel).ToString, nil);
Ffrm.lblStock.caption := (obj as TDomainModel).Data.Stock;
Ffrm.lblClient.caption := (obj as TDomainModel).Data.Client;
Ffrm.lblSize.caption := FloatToStr((obj as TDomainModel).Data.Size);
Ffrm.lblPrice.caption := FloatToStr((obj as TDomainModel).Data.Price);
Ffrm.lblValue.caption := FloatToStr((obj as TDomainModel).Data.Consideration);
end;



This shows that a new view can be added, which out having to change anything in the Model or Controller.

The first two Views are associated with the main form, and show a single value from the Model, and a list view representing the changes to the order. The third View is the state of the 'whole' order.




Tuesday 28 October 2008

MVC in Delphi (Part II)

The TCommand is clearly a Command, and should be slightly different, but I will sort that out later.

John Wayne!


The 1975 John Wayne film Brannigan was filmed in London, more specifically there is a car chase that goes down the street I live in. Here is a still from the chase, showing people clearly standing around watching the action.

Monday 27 October 2008

MVC in Delphi (Part I)

We have a large Delphi software project at work, that has been in development for the last 10 years and is now basically unmaintainable. It uses threads to create conversations with an iSeries I5 (an AS/400 in old money). These threads are unmanaged, interdependant and duplicated throughout the application.

We have been on the point of refactoring many times, but have not had the time or the will-power to actually do it. So, here goes ...

MVC

Basically the MVC pattern looks closest to what I need. A data domain, and dependant views on it, so it "... isolates business logic from user interface considerations..."

The parts of the pattern are ..











Model


This is basically an Observable ...


IObservable = interface

procedure RegisterObserver (obs : IObserver);
procedure UnregisterObserver (obs : IObserver);

procedure Notify (const obj : TObject);
procedure ClearObservers ();

procedure DoCommand (cmd : TCommand);

end;


A default implementation of IObservable is the TObservable class, from which more concrete implementations should descend.


///This is the default implementation of IObservable
///IObservable
TObservable = class (TInterfacedObject,
IObservable)
private
FObservers : TClassList;
FCurrent : TObject;

public
constructor Create;
destructor Destroy; override;

procedure SetCurrent (obj: TObject);

procedure RegisterObserver (obs : IObserver);
procedure UnregisterObserver (obs : IObserver);
procedure ClearObservers ();
procedure Notify (const obj : TObject);

procedure DoCommand (cmd : TCommand); virtual;

property Current : TObject read FCurrent write FCurrent;

end;


View

This is basically an Observer ...


IObserver = interface

procedure UpdateView (obj : TObject);

end;

The IObserver and IObservable pattern is a standard pattern from Design Patterns: Elements of Reusable Object-Oriented Software. It is half way there, with the Observable registering all of the Observer objects that are interested in when it changes. However, there is not a way of interacting with the Observer, in order to make it carry out operations, this is where the Controller comes in.

Controller

The controller is used (in my code at least) register all of the Observers with the Observables, and to marshal commands from the View to the Model. My interface for a Controller looks like this;


IController = interface

function GetModel : IObservable;

procedure SetModel (const m : IObservable);

function GetView (index: integer): IObserver;

procedure SetView (index: integer; const v : IObserver);

property Model : IObservable read GetModel write SetModel;

property Views[index: integer]: IObserver read GetView write SetView;

procedure DoCommand (cmd : TCommand);

procedure AddView (v : IObserver);

end;


This maintains a list of Views (accessed via the Views property), and the Model which they are interested in. The commands are processed via the DoCommand method (this is a bit of a hack I think, and I can't quite think of a way to do this nicely!).

Concrete Example

So the interfaces above describe the basic building blocks of an MVC interface. Lets try and build a concrete example to see how it works.

My example model will be a single extended value, which will be initialised at startup by the Model, and then operations can be carried out on it by the View, via the Controller.


First we define the concrete model ...


///A small implementation of the MVC architecture
TDomainModel = class (TObservable)
private
/// Underlying data object
FData : extended;

public
///Property that represents the underlying data
property Data : extended read FData write FData;

///Executes the given command
procedure DoCommand (cmd : TCommand); override;

///Creates the underlying data
constructor Create;

///Function that is passed to the CommandThread for execution
///TCommandThread
procedure ExecuteCommand (cmd : TCommand);
end;


As can be seen, we descend from the base class
TObservable, which implements all of the IObservable methods, and provides some basic functionality needed by all TObservable objects (see above).

The DoCommand method wraps up the TCommand object (more of that later), and then spawns off a thread to actually do the processing. This hides all of the gubbins of getting data and processing it from the Controller and the View. In the real example this would get stuff from the i5 via the conversation. It would mean that this method of using threads could be changed to use a managed queue, or whatever, without changing the View or the Controller, as they don't need to know HOW the data is fetched, just that it is ready.

In the example below, we just do some very simple maths


procedure TDomainModel.DoCommand (cmd : TCommand);
var
T : TCommandThread;

begin
t := TCommandThread.Create(true);

t.Cmd := cmd;
t.ParamList := cmd.ParamList;
t.Operation := ExecuteCommand;

t.Resume;
end;

procedure TDomainModel.ExecuteCommand (cmd : TCommand);
begin
if cmd.Command = 'HALF' then
begin
FData := FData / 2;
end;

if cmd.Command = 'MULTIPLY' then
begin
FData := FData * 2;
end;

if cmd.Command = 'SET' then
begin
assert (cmd.ParamCount <> -1, 'SET requires a parameter value');
FData := cmd.Params[0].Value;
end;

if cmd.Command = 'ADD' then
begin
assert (cmd.ParamCount <> -1, 'ADD requires a parameter value');
FData := FData + cmd.Params[0].Value;
end;

if cmd.Command = 'ZERO' then
begin
FData := 0.0;
end;

Notify(self);
end;

constructor TDomainModel.Create;
begin
FData := 10.0;
end;


I'll finish off this a bit later, and upload the source for the example