SmartFactor Design: Static Architecture

As said in the introduction, SmartFactor heavily relies on two object concepts:

General Design

Objects Layout

SmartFactor is split in several parts. Each part may be represented by one Singleton. The singletons are held by SF_GLOBALS, from which inherit each class of SmartFactor.

Ah yes, one more thing: the main class of SmartFactor is SMARTFACTOR.

So, to the point: the main parts of SmartFactor are:

 
Diagram 1. General structure of SmartFactor

 
Diagram 2. Components structure of SmartFactor

SmartFactor uses a "context" to hold singletons that are set at run-time (usually at the beginning, by option setting). This context, SF_CONTEXT, contains two "settable singletons":

Directories Layout

The directories organization quite follows the objects organization.

 

SmartFactor/source

The main sources directory. Here are almost all the main abstractions of SmartFactor, along with the main class:

    

interfaces

The interfaces submodule, also holding the abstractions of the GUI.

SF_INTERFACE_FACTORY is used by the abstract layer, while SF_LOGGER is used anywhere in SmartFactor.

More on the GUI architecture

        

abstract

This directory holds all the upper-level "widgets" used by the menus. These widgets use the lower-level abstractions (in interface), which in turn are reified by three implementations (console, dialog, and graphic). It is the upper part of the Bridge.

Of those widgets, SF_ROOT has a special role: it is the GUI holder. It is a singleton held by SF_GLOBALS; it is the object SmartFactor talks to when the GUI must be "started" (beginning accepting events, after having been built).

Those widgets use an event-oriented model. It means that the GUI widgets "react" to external events (a button may do something when it is clicked upon, and so on). That "do something" is simply an agent given to the widget. One specially interesting action is agent ui.stop which makes the GUI stop accepting events.

        

console

It is the "console" low-level GUI.

        

dialog

It is the "dialog" low-level GUI. It is built using the Newt library developped by RedHat. Newt is great!

             

newt

The Newt widgets eiffelized. A lot of c_inline_c there...

        

graphic

(This GUI is not yet developped; it will be a graphical interface based on Vision.)

        

interface

It is the low-level GUI abstractions: all the low-level widgets behaviour is defined here. It is the lower part of the Bridge.

    

menu

This directory holds all the implementations of the SF_MENU and SF_REFACTORY_MENU interfaces (defined in globals).

SF_MENU is the main menu abstraction: it holds items to be displayed (in fact, it holds widgets: an SF_LIST and some SF_BUTTONs). Those menus are reified to display the available clusters af a system, the available classes of a cluster, and so on.

On the other hand, SF_REFACTORY_MENU is a special case of a menu, which holds refactories (refactoring tools to be applied on the choosen item, be it a cluster, a class or a feature).

More on the menus architecture

    

misc

Some miscellaneous classes I did not want in the main directory.

    

project

Some project-oriented classes. They are an Adapter to the main structures of SmartEiffel. They define that a project is a set of clusters (SF_CLUSTER), each cluster being a set of classes (SF_CLASS), and so on.

    

refactory

One of the most important directories, since it holds the heart of SmartFactor: the refactoring tools (in SmartFactor lingo, the refactories). All those refactories must inherit from SF_REFACTORY.

Apart from the menus, the refactories should be the only other objects in SmartFactor to make use of the GUI, though in a more diversified manner (generally they ask some questions, what's the new name of the class, are you sure you want to do that, and so on).

More on the refactories architecture

    

sorter

Some modified SmartEiffel library classes, enabling the use of an external comparator. It is mainly used by the menus to sort items like clusters or classes (which are SmartEiffel classes, thus could not be changed to add an internal comparator).

GUI Design

Introduction

There are currently three GUIs, depending on which root procedure is used to compile SmartFactor. There is also a root procedure that has an extra command-line option (viz. --interface) which allows to choose between the three available interfaces. Note though, that this last procedure slightly slows down the GUI since there are extra dynamic bindings that SmartEiffel obviously could not compute out.

The SmartFactor GUI design was made with the following idea: it must be simple to create a brand new GUI. Hence, a GUI implementation is simply a plugin component.

The GUI is split in two parts:

The upper layer

The upper layer of widgets is strictly event-driven; it means that the widgets will do something when an event is sent to them (e.g. a button may be clicked, an item may be chosen in a list, and so on). How this event is sent to them is of no concern (the low-level layers will take care of that).

One very important widget is SF_FORM, which is the blessed child of the classic ideas of a GUI widget container and a GUI widget window. Why is there only one widget for those two roles? Because SmartFactor has a very simple GUI architecture: no fancy here. Only one window displayed at a time, and no nested containers which are a difficult concept when you don't know how the widgets will be displayed.

While we are at that, some other classic GUI notions were left out: there is no notion of position, no notion of layout and those gory details. The abstraction is minimal: a widget is just a thing that can react. How it is displayed is of no concern of the rest of the world, only the low-level parts will deal with that.

Note that it also means that the low-level GUI will make assumptions on how the widgets are used by SmartFactor. For example, a form will generally contain a list and some buttons (and that makes a menu). Hence, this GUI component is an interesting idea, but maybe not general enough to be used out of SmartFactor.

(Back to the forms.) There may be more than one form: but only one is displayed at a time. Imagine they are stacked modal windows: only the top of the stack is useable. A form is normally held by an SF_ROOT, which knows when to display it.

Well then, there is this SF_ROOT thing. It is, in fact, the GUI engine of SmartFactor. It holds the stack of forms. It is a singleton object, accessible via SF_GLOBALS.ui. A classical way of using it is:

        feature simple_gui is
                local
                        b: SF_BUTTON
                do
                        ui.prepare                  -- (1)
                        create b.make
                        b.set_action(agent ui.stop) -- (2)
                        ui.root.add_child(b)
                        ui.start                    -- (3)
                        ui.finalize                 -- (4)
                end
 
Diagram 3. GUI sample code

There are a few lines to look at:

Line (1): tell the GUI that a new form is about to be displayed. The GUI creates a new SF_FORM, accessible by ui.root. You may then start adding widgets to that form.

Line (2): the action of the button is agent ui.stop which breaks the event loop.

Line (3): tell the GUI to start the event loop. It means that the program is frozen until some event occurs, which will then trigger the associated agent (in the example, click on the button to close the window).

Line (4): after the event loop was broken, you may either start it again (it will show the same form), or destroy the form using the finalize feature (this allows to free the underlying structures held by the operating system or whetevr it is that displays the GUI).

See also SF_MENU.show.

The lower layer

The deferred set

The lower layer consists in one or more GUI plugin components. Those components must reify the interface of the deferred set, along with providing a concrete class of SF_INTERFACE_FACTORY and SF_LOGGER.

SF_INTERFACE_FACTORY is used by the upper layer to connect to the lower-level widgets. It's the classical Factory Design Pattern.

SF_LOGGER is the logger (used to display messages to the user). The logger instance is accessed via SF_INTERFACE_FACTORY.logger. More on that topic: see the logging section.

The console set

This set is a 80x25 text-only GUI. I'm quite proud of it because, not only it works well, but it also looks great. Long menus are paged, it is event-driven (yes, it is), and so on.

The difficult part of this GUI was to transform a classic menu (the good old your choice?) in events. To do that, I used the Chain Of Responsibility Design Pattern. When the user enters text, the form triggers the chain until some widget handles the request.

The Newt set

This set presents dialogs to the user. You need the Newt library for this GUI set to work (I use 0.51, let me know if older versions work too...)

The transformation was quite straightforward. All the GUI had to do was some layout management. Knowing the SmartFactor restrictions, it was quite simple.

About the layout of the components: the model was quite outrageously simplified. It is centralized in SF_DIALOG_FORM. Nesting forms has the effect of changing the layout direction: either they are layed out horizontally or vertically. The upper "level" (the window) being layed out verticadlly. To understand why it is so: look at the menus aspect: a list and a bunch of buttons below, but the buttons are one beside another. I chose not to introduce "layout" objects for the sake of simplicity. I may still refactor in the future ;-)

Newt is basically not event-driven (though I'm aware you could add that sort of behaviour, I'm not sure how well it reacts with Eiffel): therefore, I developped the same kind of Chain of Responsibility as for the console GUI set.

The graphical set

Though not yet developped, this set will use Vision, for two reasons: first, it is architecture-independant (works as well on X11 and Win32), and second, its model is quite the same as mine (event-driven, notion of stacked event loops).

Notes on the design---a new design pattern

The design may look sloppy because there is an inheritance diamond. Such a diamond is not allowed by SmartEiffel 2.0. Still, the way the Design Patterns are used (Composite plus Bridge plus Chain of Responsibility plus Factory) makes this diamond quite compulsory since the classes are tightly coupled. The diamond would look like, (taking the button widget as example):

 
Diagram 4. The would-be inheritance diamond

The problem is the following:

But some breaks must be introduced. To solve the problem, we introduce a new design pattern: the split object that allows an object to behave as it it had two conformant parents. The Split Object is an object pair, each referring to the other. The resulting diagram is the following:

 
Diagram 5. The Split Object Design Pattern
(Note that "engine" is ill-chosen for such a technical role. I kept it for history reasons, simply because I don't like to tinker too much with renaming files in CVS...)

Why does it solve the problem: look at the broken branch: SF_CONSOLE_WIDGET does not conform anymore to SF_INTERFACE_WIDGET. It means that the parent-child relation looks broken. To restore it:

        add_child (a_child: SF_INTERFACE_WIDGET[SF_CONSOLE_FACTORY]) is
                local
                        e: SF_CONSOLE_ENGINE
                do
                        e ?= a_child.engine -- SmartEiffel is smart enough to change that in a straight assignment
                        children.add_last(e.widget)
                end
            
 
Diagram 6. add_child code

More on this pattern: It should be seldom used. Real "hard" diamond are few, but one can find them when trying to use a tight combination of Design Patterns (like here). In most cases, simply breaking the diamond should be enough.

Logging Design

It is the classic Logger pattern: one Singleton, some log levels, and so on.

To ease the memory pressure though (i.e. avoid that horrible infix "+" feature of STRING), not only is there a single log feature (which exists for simple cases), but also a whole protocol that lets you build the message in parts before displaying it.

In fact, the logger is an OUTPUT_STREAM (of the standard SmartEiffel library), hence all the features such as put_integer and so on are available. The only restrictions for those features are:

The known log levels are defined in SF_LOGGER:

Menus Design

Here also, the design was simplified. At the most generic level, a menu is just an SF_LIST and two SF_BUTTONs (quit and refactor). A menu also holds an SF_REFACTORY_MENU.

The list contains the items of the menu, depending on the menu itself.

The quit button is always displayed, and allows to exit the menu.

The refactor button is displayed if the refactory menu is not Void; if so, the button allows to display that menu.

 
Diagram 7. Menus structure

Refactory menus are more specialized: their appearance is totally defined by the refactories they hold.

The menu architecture is the following:

Refactories Design

Refactories are executed via the refactory menus. Their design vary from one refactory to another, depending on the work they have to do.

Some hints on how to add new refactories:

Rename a class

SF_RENAME_CLASS_REFACTORY allows the user to rename the selected class. It asks for the name and after some checks (name not already used, and so on) it renames the class, and all the entities throughout the project that reference that class, all the inheritance paths, and so on.

Rename a feature

SF_RENAME_FEATURE_REFACTORY allows to rename a feature. (Not yet implemented).

Previous TOC Next


Contact me
Last modified: Wed Sep 22 12:19:47 CEST 2004