• Ingen resultater fundet

Architecture and design

In document Intended audience (Sider 30-35)

2. Imaging System Development

2.5 Architecture and design

Throughout our lives, we gain experience - mostly by doing things wrong. In software development, this is also true, but sometimes you can't afford to do things wrong, and when you may finally see that some part of your architecture will break under some stressed situations, you may try to patch things up to save time – or that is.... you THINK you save time – but that's a whole different discussion (see the section on refactoring).

Creating a good design is essential to making a robust and flexible system, and a few techniques to help in the process are presented in this section.

2.5.1 Using Design Patterns

In the early 1990s, Erich Gamma, Richard Helm, John Vlissides and Ralph Johnson addressed this challenge and began to work on one of the most influential computer books of our time [Grand98, p. 1-5]: “Design Patterns”. These patterns are reusable solutions to recurring problems that occur during software development, collected in a cataloged fashion and given a name for easier recognition among developers. To give an idea of how developing using software

patterns works, suppose that you need to write a class that manages motor control by encapsulating low level functionality, and providing a high level public interface to users of the class. In this case, you would not want more than one instance of the class to exist at any given time, as it might give unpredictable results, but how to manage this?

The Singleton pattern [Grand98, p.127] handles this kind of situation by controlling the instantiation mechanism internally, using a static member variable, thereby ensuring that there will never exist more than one instance.

Continuing the example of the motor controller, to implement a class using the Singleton pattern is fairly simple, as you can see from the diagram in figure 6, and the accompanying implementation example in source listing 1.

Walking through the code example, notice that the class constructor has been made unavailable to the public (users of the class), forcing outside code to access it through the static getInstance() method. When using the MotorControl class, the public methods would be accessed by going through that function, as shown in source listing 2.

This way, you don't have to worry about the motor controlling methods being used in an unmanaged way.

Using patterns in general when developing software also helps when trying to figure out why the system might not behave as you expect it to and requires

Figure 6, UML diagram of the MotorControl class using the Singleton pattern.

MotorControl

-m_instance: MotorControl = null -MotorControl()

+getInstance(): MotorControl +setSpeed(speed:double): void

+setDirection(direction:boolean): void Return the class scoped (unique) m_instance.

If m_instance is ’null’, then m_instance is first created by calling the private constructor, MotorControl().

debugging. Here, the patterns - if used correctly - ensure that the more common design mistakes can be ruled out.

public class MotorControl {

// An object to hold the class instance when created private static MotorControl m_instance = null;

// A private constructor, not callable from outside this class private MotorControl(){

// initialize contact with low level motor control }

// The public instantiation function, // synchronized to ensure thread safety

synchronized public static MotorControl getInstance(){

if( null == m_instance ){

// If this is the first call to getInstance, create the object m_instance = new MotorControl();

}

// Return the instance object return m_instance;

}

public void setSpeed(double speed){

// set the speed...

}

public static final boolean LEFT = false;

public static final boolean RIGHT = true;

public void setDirection(boolean direction){

// set the direction...

} }

Source 1, MotorControl class - an example of the Singleton pattern in use.

MotorControl ctrl = MotorControl.getInstance();

ctrl.setSpeed( 0.0 );

ctrl.setDirection( MotorControl.LEFT );

ctrl.setSpeed( 100.0 );

Source 2, All interaction with the MotorControl goes through the getInstance() method.

2.5.2 Model-View-Controller

Throughout the design and implementation, it has been an overall goal to separate program logic and graphical user interface. To achieve this, the Model-View-Controller (MVC) pattern [Buschmann96, p.125] has been perfect.

Basically, the goal of using the MVC pattern, is to make a clear distinction of what part of the code belongs to the core data storing, processing and handling, and what part of the code belongs to the current choice of visualization mechanism (e.g.

GUI based, text based, printer output, etc.).

To better understand the split between model, view and controller, take a look at the diagram in figure 7, illustrating the different roles as well as ways they can communicate with one another9.

Model: Representing a data container, as well as the business rules defined for accessing and updating the data.

View: The view renders the contents of a model. It is the view's responsibility to maintain consistency in its presentation when the model changes. This can be achieved by using a push model, where the view registers itself with the model

9 From a web page on J2EE patterns by Sun Microsystems, Inc.

URL: “http://java.sun.com/blueprints/patterns/MVC-detailed.html”.

Figure 7, Box diagram of the Model-View-Controller pattern.

for change notifications, or a pull model, where the view is responsible for calling the model when it needs to retrieve the most current data.

Controller: The controller translates interactions with the view into actions to be performed by the model. In a stand-alone GUI client, user interactions could be button clicks or menu selections. The actions performed by the controller include activating business processes or changing the state of the model.

The module plug-in loading mechanism has been designed using this pattern, as illustrated in figure 8.

In the current system implementation, it is possible to swap between two versions of the pluggable module loading mechanism:

Figure 8, UML diagram showing the structure of the Model-View-Controller pattern applied to the plug-in loading mechanism.

PluginLoaderModel -m_loadedPlugins: Vector -m_listenerList: EventListenerList -m_changeEvent: ChangeEvent +PluginLoaderModule()

+addChangeListener(ChangeListener): void +removeChangeListener(Changelistener): void

#fireChangeEvent(): void

-loadPluginAtRuntime(File): BasicPlugin +scanForPlugins(String): void +getLoadedPlugins(): Iterator

PluginLoaderView -m_model: PluginLoaderModel -m_rescanButton: JButton

+PluginLoaderView(PluginLoaderModel) -repaintPluginList(): void +stateChanged(ChangeEvent): void

PluginLoaderControl -m_model: PluginLoaderModel

-m_view: PluginLoaderView

+PluginLoaderControl(PluginLoaderModel,PluginLoaderView) +actionPerformed(ActionEvent): void

<<interface>>

ChangeListener +stateChanged(ChangeEvent): void

<<interface>>

ActionListener +actionPerformed(ActionEvent): void

1. PluginLoaderModel: The basic module loading mechanism, assuming that all modules are located in separate Java library (*.jar) files, either on a remote server or on the local file system. This model is mainly used when deploying release versions of the system.

2. PluginInternalModel: A version of the module loader, mainly used under development. This model will load all modules currently available in the development environment, thereby eliminating the need for building *.jar library files for every change made to a module.

Because of the MVC architecture used, the difficulties involved in swapping between the two models are very limited, and does not affect the view nor the controller classes.

In document Intended audience (Sider 30-35)