© John Kouraklis 2016

John Kouraklis, MVVM in Delphi, 10.1007/978-1-4842-2214-0_4

4. Two-Way Communication

John Kouraklis

(1)London, UK

Take a moment to revisit the code developed for POSApp so far. In particular, try to identify the way that communication is being delivered among the View, the ViewModel, and the Model. Then consider Figure 3-4 and the way the initial labels captions are retrieved. Based on this figure and the description of the layout of the MVVM model as presented in Chapter 1, we may notice that each one of the components communicate with the next in the layout in an one-way arrangement, as awareness of each element is limited. The View is aware of the ViewModel, but the ViewModel doesn’t know anything about the View. Likewise, the ViewModel is aware of the Model, but not vice versa. This design has served us well in POSApp up to this point, as we simply wanted to get the values of the labels.

However, if we go back and run the version of POSApp developed in Chapter 2, we will notice that the Total Sales figure in the MainScreen is updated every time a new invoice is successfully issued. This implies that there is the need for a communication channel that ends up to the View in addition to the one already in place, which originates from the View and is described by the MVVM model (see Figure 4-1). In this case, how do we solve this two-way communication requirement?

A371064_1_En_4_Fig1_HTML.jpg
Figure 4-1. Two-way communication requirement

The Provider-Subscriber (ProSu) Framework

There are many acceptable approaches to address this new challenge. We could create a variable or even a property in the view of the InvoiceForm and directly link it to the label that represents the Total Sales figure in the MainScreenForm (LabelTotalSalesFigure). Then, before the InvoiceForm closes, it would update the LabelTotalSalesFigure with the new value. Although this is a common approach, we will opt out here, because that would create a tight linkage between two elements, leaving us with strong dependencies. Instead, we will turn to patterns that implement looser coupling among a different (and unknown) number of elements.

The pattern we will be looking at this section is usually referred to as the observer pattern. This is a one-to-many communication of change of states between objects that are loosely coupled. The pattern is well explored in a variety of sources, with the most notable being Gamma et. al. (1994). It’s presented in Figure 4-2. In this pattern, a class that acts as provider of messages (or publisher as it is also called) sends messages to a number of classes (observers). The interpretation and, consequently, the course of action to be taken by the transmitted messages is the sole duty of the observer.

A371064_1_En_4_Fig2_HTML.jpg
Figure 4-2. Provider-subscriber design pattern

For our needs, we are going to develop our own implementation instead of using a ready-made one. We shouldn't see this as restrictive; if we are familiar with an alternative implementation, feel free to use it.

Let's call this version the ProSu Framework (Provider-Subscriber). For a number of reasons that don't fall in the scope of this book and this chapter, a very efficient and flexible way to implement this pattern is to employ interfaces.

Note

An interface works like a class that holds well-defined procedures and functions but doesn't provide the implementation of those methods; instead, it works as a blueprint to indicate what we should expect to see in terms of procedures and functions in a class that uses the interface. Interfaces are implemented in real classes and they allow the implementation of multi-inheritance classes in Delphi. If you need a refresher on the topic of interfaces, visit the documentation that comes with your version of Delphi or follow this resource (DocWiki, 2015).

Let's return to the PosAppMVVMMainForm project. The ProSu units will support our application. In order to keep our housekeeping at good levels, we will place them in separate folders.

  1. Go to the folder where we have saved the project and the project group.

  2. Create a new folder called SupportCode.

Figure 4-3 summarizes how the ProSu framework works. Any class (subscriber) that needs to follow messages from the provider subscribes to it using the Subscribe method of the provider (Step 1). Whenever the provider wants to alert the subscribers, it sends out a signal using the NotifySubscriber method (Step 2).

A371064_1_En_4_Fig3_HTML.jpg
Figure 4-3. Provider-subscriber implementation details

This method invokes the UpdateSubscriber method at the subscriber side (Step 3). You will notice in Figure 4-3 that the subscriber has an UpdateMethod property and a SetUpdateSubscriberMethod. This is because we want to make the subscriber class to adapt to any classes in the code (as we want the same for the provider class). This means that the actual method the subscriber uses to respond to signals from the provider is not known in advance but it is class-dependent. SetUpdateSubscriberMethod and UpdateMethod provide a way to implement this, as we will see in the next lines of code. Overall, this adaptability of the framework to unknown classes is built on the flexibility of the interfaces. Now, let's dive into the code.

In the POSAppMVVMMainForm project, add a new unit called Model.ProSu.Interfaces.pas. (Remember? I said we’d try to keep the code as tidy as possible.) Save the unit in the SourceCode folder we created earlier. Then, add the following code.

A371064_1_En_4_Figa_HTML.gif

This unit defines the interfaces for the framework. Follow the same steps when we created the Model.ProSu.Interfaces.pas unit and create a new unit called Model.ProSu.Provider.pas with the following

A371064_1_En_4_Figb_HTML.gif
A371064_1_En_4_Figc_HTML.gif

We define the ProSuProvider class, which implements the IProviderInterface and represents the provider. We need a way to keep track of the subscribers and we’ll do so by introducing a private TList variable. Then, we implement the Subscribe, Unsubscribe, and NotifySubscribers methods by manipulating the TList<T> property. Subscribe adds an interfaced subscriber to the list, Unsubscribe removes it, and NotifySubscribers invokes the UpdateSubscriber method of the subscriber. Apart from this, the Create and Destroy methods take care the fSubscriberList.

The last part of the framework deals with the subscriber. We need to create one more unit as we did earlier (Model.ProSu.Subscriber.pas) and implement it according to the following code. The subscriber unit is straightforward and simple.

unit Model.ProSu.Subscriber;

interface

uses Model.ProSu.Interfaces;

type
  TProSuSubscriber = class (TInterfacedObject, ISubscriberInterface)
  private
    fUpdateMethod: TUpdateSubscriberMethod;
  public
    procedure UpdateSubscriber (const notifyClass: INotificationClass);
    procedure SetUpdateSubscriberMethod (newMethod: TUpdateSubscriberMethod);
  end;


implementation

{ TProSuSubscriber }

procedure TProSuSubscriber.SetUpdateSubscriberMethod(
  newMethod: TUpdateSubscriberMethod);
begin
  fUpdateMethod:=newMethod;
end;


procedure TProSuSubscriber.UpdateSubscriber(const notifyClass: INotificationClass);
begin
  if Assigned(fUpdateMethod) then
    fUpdateMethod(notifyClass);
end;


end.

Two-Way Communication (Revisited)

We developed the ProSu framework as a response to the need we identified earlier for the View (MainForm) to receive notifications in addition to its ability to initiate communication with the ViewModel. The observer pattern does exactly this; it allows the View to receive notifications from the InvoiceForm to update the Total Sales figure.

Let’s see how this works. We are going to use a simple form to imitate the functionality of printing an invoice and we will investigate how to make the MainForm update the sales figure. We can find the full code in the POSAppMVVMMainFormTest folder in the book’s files.

  1. Add a new form in the POSAppMVVMMainForm project, change the name to TestPrintInvoiceForm, and save it in the Views folder with the name View.TestPrintInvoice.pas.

  2. Add a new button (TButton) rename it to ButtonPrintInvoice and change the text property to Print Invoice. The purpose of the button is to simulate the process of completing an invoice as we have it in the InvoiceForm. The TestPrintInvoice button will notify the MainForm with the new updated sales figure and will close the TestPrintInvoiceForm.

  3. The design of the TestPrintInvoiceForm and the properties of the ButtonPrintInvoice are not important at this point.

  4. Using Figures 4-1 and 4-3, we can draw comparisons and assign the role of the observer to the MainForm and the role of the provider to the TestPrintInvoiceForm in order to bring the two forms together under the ProSu paradigm.

Switch to the code view of the TestPrintInvoiceForm and declare a private variable as IProviderInterface, which is exposed as a read-only property. Then, add the following code to the FormCreate event. We also need to add the Model.ProSu.Interfaces and Model.ProSu.Provider units to the uses clauses.

interface

uses
  ..., Model.ProSu.Interfaces, Model.ProSu.Provider;


type
  TTestPrintInvoiceForm = class(TForm)
    ...
    procedure FormCreate(Sender: TObject);
  private
    fProvider: IProviderInterface;
  public
    property Provider: IProviderInterface read fProvider;
  end;


implementation

uses Model.ProSu.Provider;

procedure TTestPrintInvoiceForm.FormCreate(Sender: TObject);
begin
  fProvider:=TProSuProvider.Create;
end;

We don’t need to release the fProvider in the FormDestroy event because interfaces manage their own lifecycles. What we need to do as the last step is write code in the OnClick event of the PrintInvoice button. The event will use the fProvider to update the subscribers. Basically, what we need to tell the subscribers has two parts: update the Total Sales figure’s label and supply the new value of the total sales.

In order to achieve this, we need to look at the declaration of the NotifySubscribers in the Model.ProSu.Interfaces unit. The declaration of the method shows that we can only pass an INotificationClass interface parameter. This is quite fortunate as the implementation of the interface is totally abstract. Considering the flexibility of interfaces, what the INotifcationClass declaration tells us is that we can pass to the subscribers any class we need as long as it implements the INotificationClass interface, which by definition is empty.

Add a new unit to the project, save it in the SupportCode folder, and name it Model.ProSu.InterfaceActions. This unit will provide a reference to any actions we want subscribers to perform. Add the following code to this new unit.

unit Model.ProSu.InterfaceActions;

interface

type
  TInterfaceAction = (actUpdateTotalSalesFigure);
  TInterfaceActions = set of TInterfaceAction;


implementation

end.

The only thing we do in this unit is to declare available actions to the subscribers. At the moment, we have only one (UpdateTotalSalesFigure), but the following chapters will introduce more. We have also declared a set of actions in order to allow subscribers to perform more than one action with only one call from the provider.

We now need to declare the notification class. Open the Model.Declarations unit, add a reference to the Model.ProSu.InterfaceActions and Model.ProSu.Interfaces units, and declare the following class.

A371064_1_En_4_Figd_HTML.gif

Back to the TestPrintInvoiceForm, implement the OnClick event of the ButtonPrintInvoice. We also need to add the Model.Declarations and Model.ProSu.InterfaceActions units.

interface

...

type
  TTestPrintInvoiceForm = class(TForm)
    ...
    procedure ButtonPrintInvoiceClick(Sender: TObject);
  private
    ...
  public
    ...
  end;


...
implementation
...


uses ..., Model.Declarations, Model.ProSu.InterfaceActions;

procedure TTestPrintInvoiceForm.ButtonPrintInvoiceClick(Sender: TObject);
var
  tmpNotificationClass: TNotificationClass;
begin
  tmpNotificationClass:=TNotificationClass.Create;
  try
    tmpNotificationClass.Actions:=[actUpdateTotalSalesFigure];
    tmpNotificationClass.ActionValue:=Random(1300);
    fProvider.NotifySubscribers(tmpNotificationClass);
  finally
    tmpNotificationClass.Free;
  end;
end;

It is time now to test whether the ProSu framework does the job. Open the View.MainForm unit and declare a new private variable to indicate that this form has the role of a subscriber in the ProSu domain.

interface

uses
  ..., Model.ProSu.Interfaces;


type
  TMainForm = class(TForm)
    ...
  private
    ...
    fSubscriber: ISubscriberInterface;    
    ...
  public
    ...
  end;


...
implementation
...
uses
  Model.ProSu.Subscriber;


procedure TMainForm.FormCreate(Sender: TObject);
...
begin
  ...
  fSubscriber:=TProSuSubscriber.Create;
end;

Create the OnClick event of the ButtonInvoice and add the following code, which creates the TestPrintInvoiceForm and subscribes the MainForm to it.

A371064_1_En_4_Fige_HTML.gif

If we compile and execute POSAppMVVM, we will be able to open many test forms. However, we will not see the Total Sales figure label updated when we press the Print Invoice button. This is because, up to this point, we notify the subscriber (MainForm) regarding a change to the sales value, but we do not process this signal in the MainForm. In fact, we are one step behind the processing of the signal. The form is not yet aware that there is a message to be processed. This is what we are going to fix next.

When we developed the IProSuSubscriber interface and the TProSuSubscriber class, we implemented SetUpdateSubscriberMethod. This method will help us resolve the problem we are facing here. We need to declare a procedure in the subscriber (MainForm) and pass it to the provider; then, when a message needs to be delivered, the provider simply invokes this method.

In the View.MainForm unit, declare the NotificationFromProvider procedure and pass it to the provider.

A371064_1_En_4_Figf_HTML.gif

Now, we develop the NotificationFromProvider method to update the Total Sales figure label.

procedure TMainForm.NotificationFromProvider(
  const notifyClass: INotificationClass);
var
  tmpNotifClass: TNotificationClass;
begin
  if notifyClass is TNotificationClass then
  begin
    tmpNotifClass:=notifyClass as TNotificationClass;
    if actUpdateTotalSalesFigure in tmpNotifClass.Actions then
      LabelTotalSalesFigure.Text:=format('%10.2f',[tmpNotifClass.ActionValue]);


  end;
end;

We first translate notifyClass to TNotificationClass (typecast) and then check for the required action from the provider. If the action is actUpdateTotalSalesFigure, we update the Total Sales figure label. Compile and execute the application. Open a couple of invoice forms and click on the Print Invoice button. You should be able to see the Total Sales figure changing in the main form.

Note

The way we processed the message from the provider violates the MVVM design, as we bypassed the step where we retrieve the value from the ViewModel. Normally, we should call the UpdateTotalSalesFigure method. We didn’t do this here because the aim was just to test the ProSu framework and confirm that messages can be passed back to the View. In the following material, we will revert to the use of the ViewModel.

We have tested the ProSu framework and now have the tools to establish two-way communication between the View and the ViewModel and, similarly, between the ViewModel and the Model. We don’t need the TestPrintInvoiceForm unit any more, so you can either remove it from the project or just remove the code in the ButtonInvoiceClick method. In the code files that come with the book, you can load the POSAppMVVMInterfaces project.

Making the Code More Efficient

We have set up a way to organize the code, defined the different responsibilities and actions of the elements of the MVVM, and established two-way communication of the several elements. Before we move on and convert the InvoiceForm to the MVVM paradigm, I will discuss some changes to the code which allow for more efficient coding. This step is not vital for MVVM but it is good practice. It follows modern code architecture and fits nicely in our case. We will use interfaces extensively in the next chapter. This section introduces a form of structuring the code that hides the actual classes deep in the implementation section of the units and exposes them with interfaces.

Note

There are many articles and sources you can consult regarding the advantages of interfaces. I recommend reading this article (Hodges, 2016) and this excellent example of the S.O.L.I.D principles (Csaba, 2013). The latter article puts interfaces in a broader context in software development.

We’ll start with the Model.Database unit and will expose the TDatabase class via an interface. In order to do this, we’ll declare an interface in the interface section of the unit and will move the class declaration to the implementation section. Then, a function is required to access the class.

  1. Open Model.Database unit.

  2. Move the TDatabase declaration in the Implementation section.

  3. In the Interface section of the unit, declare this interface.

    type
      IDatabaseInterface = interface
      ['{DDE3E13A-0EC5-4712-B068-9B510977CF71}']
        function GetCustomerList: TObjectList<TCustomer>;
        function GetCustomerFromName(const nameStr: string): TCustomer;
        function GetItems: TObjectList<TItem>;
        function GetItemFromDescription(const desc: string): TItem;
        function GetItemFromID(const id: Integer): TItem;
        function GetTotalSales: Currency;
        procedure SaveCurrentSales(const currentSales: Currency);
      end;
  4. Change the declaration part of TDatabase to the following:

    type
      TDatabase = class (TInterfacedObject, IDatabaseInterface)
  5. In the Interface section, declare the following function:

    function CreateDatabaseClass: IDatabaseInterface;
  6. In the Implementation section, develop the CreateDatabaseClass function:

    function CreateDatabaseClass: IDatabaseInterface;
    begin
      result:=TDatabase.Create;
    end;
  7. Every time we need a TDatabase class, we will declare the IDatabaseInterface and use the CreateDatabaseClass.

  8. Open the Model.Main unit and change the type of fDatabase.

    A371064_1_En_4_Figg_HTML.gif
  9. In TMainModel.Create, change the line where your create the TDatabase class. Your code should look like this:

    A371064_1_En_4_Figh_HTML.gif
  10. Interfaces don’t need to be freed explicitly, as they can manage their lifecycle. This means that we don’t need the fDatabase.Free line in the destructor of the TMainModel class. You can delete the whole destructor method.

Next, in the Model.Main unit, follow Steps to 2-9 in order to convert the TMainModel class. Your unit now looks like this:

unit Model.Main;

interface

uses Model.Declarations, Model.Database;

type
  IMainModelInterface = interface
  ['{345910DF-654D-43CF-BDE8-E708A9F33624}']
    function GetMainFormLabelsText: TMainFormLabelsText;
    function GetTotalSales: Currency;
  end;


function CreateMainModelClass: IMainModelInterface;

implementation

uses
  System.SysUtils;


type
  TMainModel = class (TInterfacedObject, IMainModelInterface)
  private
    fMainFormLabelsText: TMainFormLabelsText;
    fDatabase: IDatabaseInterface;
  public
    function GetMainFormLabelsText: TMainFormLabelsText;
    function GetTotalSales: Currency;
    constructor Create;
  end;


{ TMainModel }

...

function CreateMainModelClass: IMainModelInterface;
begin
  result:=TMainModel.Create;
end;

We now need to change the way we use IMainModel in the source code of the project (select the .exe file in the Project Manager and press Ctrl+V).

A371064_1_En_4_Figi_HTML.gif

The last piece of code we need to change in order to accommodate the new way to use the TMainModel class is in ViewModel.Main.

unit ViewModel.Main;

interface

...

type
  TMainViewModel = class
  private
    fModel: IMainModelInterface;
    procedure SetModel (const newModel: IMainModelInterface);
    ...
  public
    property Model: IMainModelInterface read fModel write SetModel;
    ...
  end;


implementation

procedure TMainViewModel.SetModel(const newModel: IMainModelInterface);
begin
  fModel:=newModel;
end;

If you execute POSApp, you should be able to see the MainForm without getting any errors during the compilation. You can now change all the classes created so far using the approach we developed so far. I don’t include all the changes here, as they are redundant and would take up lots of space. You can find the new versions of the files in the supplied code (POSAppMVVMMainFormFullInterfaces). In summary, these are the changes compared to the old code. The parentheses show the name of the interface and the name of the function that creates the class.

Model.ProSu.Provider:

  1. Move TProSuProvider to the Implementation section.

  2. Add a CreateProSuProviderClass function.

Model.ProSu.Subscriber:

  1. Move TProSuSubscriber to the Implementation section.

  2. Add a CreateProSuSubscriberClass function.

ViewModel.Main:

  1. TMainViewModel (IMainViewModelInterface; CreateMainViewModelClass)

  2. Property Model has a getter GetModel function.

  3. Property LabelsText has a getter GetLabelsText function.

View.MainForm:

  1. The fViewModel property in the TMainForm class is declared as IMainViewModelInterface.

  2. The fSubscriber property in the TMainForm class is declared as ISubscriberInterface.

  3. The property ViewModel is declared as IMainViewModelInterface.

  4. Use procedure SetViewModel (const newViewModel: IMainViewModelInterface) in both the definition of the class and the implementation of the procedure.

  5. In the project file, change the declaration of mainViewModel:

    var
             ...
    mainViewModel: IMainViewModelInterface;
  6. In the project file, mainViewModel:=CreateMainViewModelClass;

  7. In TMainForm.Create, fSubscriber:= CreateProSuSubscriberClass;

  8. Remove the TMainForm.FormDestroy event.

One last move before we continue to the InvoiceForm; when we developed the ProSu units, we created a dedicated unit to keep all the declarations of the interfaces. This is good practice and now we will also move the interface declarations of the Model, the ViewModel, and the database to a new unit.

  1. Create a new unit and save it as Model.Interfaces in the Models folder.

  2. Move the IDatabaseInterface declaration from the Model.Database unit to the Model.Interfaces unit.

  3. Add the Model.Declarations unit in the uses clause of Model.Databases.

  4. Add System.Generics.Collections in the uses clause of Model.Interfaces.

  5. Add Model.Interfaces in the Implementation uses clause of Model.Main.

  6. Move the IMainModelInterface declaration from the Model.Main unit to Model.Interfaces unit.

  7. Add the Model.Interfaces unit in the Interface uses clause of Model.Main.

  8. Add the Model.Interfaces unit in the Implementation uses clause of ViewModel.Main.

  9. Move the IMainViewModelInterface declaration from the ViewModel.Main unit to the Model.Interfaces unit.

  10. Add the Model.Interfaces unit in the uses clause of the View.MainForm file.

Note

At this stage, we have organized our code into small, manageable units. Interfaces helped a lot in this regard. We converted all the core classes that are linked to the MVVM; however, we didn’t touch the classes in the Model.Declarations unit. In a real-world application, you may also want to use interfaces for those classes. In the rest of the book, we will use the original version of Model.Declarations (without interfaces), because converting those classes to use interfaces requires additional work that is not related to the scope of this book. This can be a challenge to you!

Summary

The ProSu framework (observer pattern) provides a way to establish bi-directional communication between the different components of the MVVM. Basic implementation of S.O.L.I.D principles and the knowledge and methodology we developed in the previous chapters provide the tools to continue the conversion of the last part of the application, the InvoiceForm. This is the focus of the next chapter.

References

Csaba, P., 2013. “The SOLID Principles: Envato Tuts+ Code Tutorials,” available at http://code.tutsplus.com/series/the-solid-principles--cms-634 .

DocWiki, 2015. “Interface References (Delphi),” available at http://docwiki.embarcadero.com/RADStudio/Seattle/en/Interface_References [Accessed 08/07/ 2016].

Gamma, E., Helm, R., Johnson, R., Vlissidis, J., and Booch, G., 1994. Design Patterns: Elements of Resusable Object-Oriented Software, Addison-Wesley Professional.

Hodges, N., 2016. “Why you Should Be Using Interfaces and Not Direct References,” Available at http://www.nickhodges.com/page/Why-You-Should-be-Using-Interfaces-and-not-Direct-References.aspx [Accessed 24/06/2016].

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset