A design story

Background context

I'm one of several programmers in the the Clinical Trials Service Unit. The work we do supports medical research, in particular for clinical trials. For example, you might have a trial to discover "Is that drug better for patients than this drug? Or better than no drug at all?" In our building, there are labs - proper labs, with lab technicians in white coats, and test tubes! - where testing is done on biological samples, for example you might do a cholesterol test on a sample of blood. There is a bunch of in-house software to support the work of the labs; that's why there are lots of programmers around.

One of the pieces of software, that I'm working on, is for supporting the lab technicians as they work with the big analyser machines that run a whole bunch of tests on trays of little vials containing samples. See, you can't just run the tests and log the results, you need to do quality control checks on the analyser, also giving it quality control solutions to analyse, where you know what the results are supposed to be, so you can check that the analyser is working ok and that the test results are valid. The rules (e.g. the Westgard rules, which you can google, though this is a sidetrack of a sidetrack...) for checking whether something is valid are pretty complicated, but semi-automatable, which is why it would be handy to have software to assist with the validation.

Programming context

The programmers are myself and P, the other programmer who is the project leader. We're programming in C++ (not that that matters for this story). P has been doing the business layer, where the interaction happens between this validation software and the database that holds the test results sent from the analysers. My contribution is the GUI.

The problem

Due to the testing & quality control being quite complicated, the software also ends up being quite complicated, with a lot of different concepts/physical objects getting represented on the screen (e.g. samples, test results), so very OO-suited. Many conceptual objects get manipulated at once (100s), and the lab technicians can view the information in various different ways and do an assortment of different actions (e.g. schedule a test to be rerun). So the GUI for the program is large and complicated.

Problems started when I kept getting pulled away from the project to go and work on other projects (utilities for Biobank - perhaps you have heard of Biobank? Anyway Biobank has big important clout so it keeps getting higher priority), so the GUI development ends up falling behind the facilities provided by the business layer. Not good.

So the problem as originally encountered is "How do we organise the GUI code so that it can be worked on by more than one programmer (so that P can work on it as well as myself)?", but also, we wanted to solve "How do we keep the GUI code well structured and so that it doesn't end up as a tangled mass of spaghetti?"

A solution

We do use MVC, and certainly helps a certain amount in spaghetti prevention, but this isn't the answer for this story (it doesn't by itself do enough modularising for separate programmers to work on it).

So the interface is divided up into various sections, typically different panels with associated visual components, and different programmers work on different sections. For example, I might be working on the panel displayed higher up on the first tab; P might be working on what the third tab is showing. Each of these sections has a "view" class and a "controller" class, and communicates with the central "model" class.

P had a neat idea about how to structure the communication between the different sections.

The sections are pretty independent; they don't worry about what the other sections are up to.

Instead, the Model has associated with it a list of components that are listening out for interesting events.

The various sections register themselves as listeners, and then when interesting events happen, they hear that the events have happened, and respond somehow, maybe changing their display.

In turn, depending on what user interaction happens with that section of code, a section can give notification of an event, which will get communicated elsewhere via the list of listeners. So all communication goes through the list of listeners that the Model has.

For example, I'm working on the Snapshot section, which displays all the available test results for all the samples, and all the queued tests and pending test results. There is a method


void SnapshotController::notifySelected(int worklistEntryId)
{
    model->setSelectedWorklistEntry(worklistEntryId);
}

Here a "worklist entry" represents a test that will be carried on a sample (a queued test), or a test that is currently being carried out on a sample (pending), or a test that has already been done and has a result.

Say I run the program and I click on a worklist entry in the snapshot section, then this will call the SnapshotController::notifySelected method to say this worklist entry has just been selected (the selection of a worklist entry being an event).

Similarly, if a notification is received, then it gets handled by the method

   
void SnapshotController::notify(int modelEvent, const EventData &eventData)
{
    switch(modelEvent) {
        case MODEL_EVENT::WORKLIST_ENTRY_SELECTION_CHANGE : snapshotView->changeSelected(eventData); break;
        ... other cases go here for other events ...
       
    }
}   
   
Here the EventData object will contain the worklistEntryId

Other sections will have similar methods. They will all satisfy the same interface specification ("interface" used in the technical OO sense, e.g. how Java uses it, I don't mean the meaning with a GUI flavour) for receiving notifications.

For example, P has a section for sample runs (a sample run is a group of different tests all performed on one sample), and it too has a

   
void SampleRunController::notify(int modelEvent, const EventData& eventData)
{
    switch(modelEvent) {
        case MODEL_EVENT::WORKLIST_ENTRY_SELECTION_CHANGE:   ...
         ...
    }
}

void SampleRunController::selectWorklistEntry(TObject* sender)
{
    ... some additional code to do a check on the worklist entry id ...
   
    model->setSelectedWorklistEntry(worklistEntryId);
}

Ok, it's probably not obvious yet why this is cool.

The neat thing that made me think you might like this example:

To illustrate, suppose I click on a test result in the SnapshotView. This generates an event corresponding to selecting that test result, and the test result is highlighted on the screen. But the SampleRunView also got notified of that, and the sample run information also gets displayed for that test result. Similarly if I click on a test result in the sample run view, then the SnapshotView also hears that event, and highlights the test result.

So this nicely modularises: I don't have to know what P's code is up to but my code can send an event and this will be heard by other sections, and they will respond appropriately, without my code having to specify explicitly what is to happen in response.


This page is maintained by Perdita Stevens (perdita@inf.ed.ac.uk)


Home : Teaching : Courses : Seoc : 2014_2015 : Schedule 

Informatics Forum, 10 Crichton Street, Edinburgh, EH8 9AB, Scotland, UK
Tel: +44 131 651 5661, Fax: +44 131 651 1426, E-mail: school-office@inf.ed.ac.uk
Please contact our webadmin with any comments or corrections. Logging and Cookies
Unless explicitly stated otherwise, all material is copyright © The University of Edinburgh