ACCU Home page ACCU Conference Page
Search Contact us ACCU at Flickr ACCU at GitHib ACCU at Google+ ACCU at Facebook ACCU at Linked-in ACCU at Twitter Skip Navigation

pinAlmost a Pattern

Overload Journal #27 - Aug 1998 + Design of applications and programs   Author: Alan Griffiths

Introduction

This article describes a recurring problem in program design and presents both a method of design and an implementation of part of that solution. The problem in question is that of separating the application logic that governs the changes a user may make to objects within the application from the detail of the user interface.

I first documented this problem and solution as part of the development of Experian's "Micromarketer" application. This formed the basis of my presentation at the AGM. Kevlin Henney informs me that he's used a similar design for a similar problem. (This makes two uses: one more and I can call it a pattern!)

The context

Many applications (including "Micromarketer") can be divided into three conceptual layers:

  • GUI

  • Business abstractions

  • Core functionality

Each of these provides services to the layers above and makes use of services provided by the layers below.

Abstraction layer components have attributes (e.g. names) that may be accessed and amended via the user interface (in the case of Micromarketer, wizards & property dialogs). The mechanisms for validating these updates should be independent of the user interface. For instance, the same component attributes may be exposed through several parts of the user interface and the validation needs to be consistent.

Some early parts of Micromarketer were developed with the validation of changes in the user interface. It has proved difficult to ensure that these remain consistent. In particular it is possible to change the name of most component in three ways: via the component "browser" (similar to "Windows Explorer"), via the component properties dialog, or via a wizard. At one stage it was possible to place "invalid" characters into a component name via the browser and to subsequently crash the property dialog by cancelling out of it.

A related problem is that changing some component attributes via wizard page/property sheet may impact another wizard page or property sheet. This could be because the value is displayed there, or because there are some options that may be enabled/disabled accordingly. In practice an approach in which the wizard pages or property sheets implement these notifications has proven error prone, hard to maintain and clearly breaks encapsulation.

Finally it may be observed that changes may not be made (and validated) directly on the component because:

  • the component may not yet exist (as in a wizard that creates the component),

  • the changes may not be complete (so that the attributes are temporarily inconsistent), or

  • because the wizard/properties may be dismissed without performing the update.

Consequently, the wizard/property dialog needs to keep a copy of the component attributes.

The Solution

The Property Template

The behaviour of a "property" is generic (and is templated on the value type):

  • it holds a value,

  • if an attempt is made to change a value then the change is "validated",

  • "interested" objects are notified of value changes.

Validation of changes will be the responsibility of the ComponentProperties class (e.g. M6XProperties). The wizard, wizard pages, and, possibly, the ComponentProperties register as "interested" objects.

Component Properties Classes

Corresponding to each abstraction layer component type (e.g. M6XComponent) there should be a ComponentProperties class (e.g. M6XProperties). This is implemented in the abstraction layer alongside the component and exposes the accessible attributes of the component as "properties".

The ComponentProperties class implements any validation methods required for the component attributes (and attaches them to the appropriate "properties"). In many cases it needs to implement a validation check that cross checks properties for consistency. This can be used to maintain an additional "isValid" property.

Each abstraction layer component has a factory method (or a constructor) and instance methods "getProperties" and "setProperties" all of which accept the corresponding ComponentProperties class.

Object interactions

When a wizard (for example) is invoked it creates an instance of the corresponding ComponentProperties object. (The ComponentProperties object could then be initialised from an existing component if this is appropriate - which is the case for a properties dialog.)

During construction the ComponentProperties object sets up the validation for any properties and also attaches listener methods to any properties that have an overall effect. (For example properties that affect the overall self-consistency of the ComponentProperties.)

The wizard adds "listener" methods on itself to selected properties. That is, to any properties that affect the wizard globally - for example requiring adding/removing wizard pages, or enabling/disabling "finish".

Each wizard page is initialised with a reference to the wizard's ComponentProperties object. It then controls the associate between dialog controls and the properties and can add "listener" methods on itself to any properties that affect the content or behaviour of the page.

When the "Finish" button is selected the component is constructed using (or has its attributes set from) the ComponentProperties object.

An outline implementation

The following code outlines an implementation of the "Property" generic used in the above solution (full source code has been was supplied - I presume it will find its way onto the C Vu disc.):

template<typename MyValueType> class M6Property
{
public:

A constructor for an unvalidated value:

M6Property(MyValueType initValue);

This constructor accepts both an initial value and an object and a method on that object that provides the validation check:

template<typename MyValidatorObject>
M6Property(
  MyValueType initValue,
  MyValidatorObject& validator, 
  int (MyValidatorObject::* method)(MyValueType));

Methods to access and modify the value. "setValue" returns a non-zero error code if the validation fails:

MyValueType getValue() const;
int setValue(MyValueType newValue);

Methods to allow objects (normally the "user interface" and the owning "component properties") to register for notification of changes to the value of the property.

template<typename MyListenerObject>
void addListener(
  MyListenerObject& listener,
  void (MyListenerObject::*method)(MyValueType));
template<typename MyListenerObject>
void delListener(MyListenerObject& listener, 
void (MyListenerObject::* method)(MyValueType));
};

Known uses

As stated in the introduction Kevlin Henney of QA says he's used a similar implementation (although he's currently too busy to give details). In addition John Merrells (the overload editor) has used a similar idea in a client server environment in which the aggregated properties are passed across the network.

References:

Diagrams are basically OMT (ISBN 0-13-630054-5 Rumbaugh et. al.) with pointless modifications by Select software.

Overload Journal #27 - Aug 1998 + Design of applications and programs