Class Hierarchy for User Interfaces

This page discusses a specification for class design and relationships for classes that control user interfaces. Since we hope to support multiple platforms and multiple types of interface for each platform (at least a web interface and a GUI on Windows, Mac OS X, and GNU/Linux), it's important to define these relationships in a way that allows:

  1. easy integration with a (presumably Django-based) backend
  2. easy extension, so that programmers wishing to add a new interface for their platform can simply write the additional classes and "plug them in"

Please keep in mind that, at the moment, these specifications are extremely preliminary, and originally conceived by an author with virtually no GUI programming experience.

 

 

Naming Conventions

The following conventions will be used throughout this document and in naming user interface classes.

  1. Class names have the form:
     <Model><Function><Framework/Toolkit><Interface object>
    where:
    • Model is the primary data model this interface deals with (e.g., Title, or Contact)

    • Function is the function this interface allows the user to perform (e.g., Display, Edit)

    • Framework/Toolkit is the framework or toolkit which implements the interface (e.g., Wx, Tk, Cocoa), or "Abstract" if this is an abstract class

    • Interface object is the type of interface object, which is presumably a "Form" but could be something else, such as a "Wizard" or "CLI"

    The purpose of this naming convention is to have an interface class name give information about its scope (i.e., the model it deals with and the framework it runs under) as well as its general inheritance pattern, while essentially reading like an English phrase.
  2. The Model is:

    • pluralized if the interface deals with multiple objects. E.g.,
        TitleEditWxForm 

      is a WxPython form for editing a single Title object, while

        TitlesDisplayWxForm 

      is a WxPython form for displaying multiple Titles.

    • "Abstract" if the class is a parent class for dealing with a variety of models
  3. The Function is one of a few pre-defined actions which expose the data to the user:

    • Display
    • Edit
    • List (?)
    • ...
  4. The Framework/Toolkit is shorthand for the GUI framework or toolkit used to implement the interface, or an indication that this is an abstract parent class which stores information common to all interfaces with the same Model and Function. Possible values include:

    • Tk (for TkInter, Python's built-in GUI library)

    • Wx (for WxPython, a third-party cross-platform Python GUI library)

    • Qt (for PyQt, another third-party cross-platform Python GUI library)

    • Django (for a Django HTML Form object)

    • Cocoa (for Cocoa, the native Mac OS X framework, via PyObjC)

    • GTK (for PyGTK, a common *nix graphical library

    • Abstract (for an abstract class)

    NOTE: I am beginning to wonder if having the framework/toolkit as part of the class name is a good idea. Given the code organization described below, where each framework and toolkit has its own sub-package for a given app, I'm wondering if it's better to let the namespace alone determine the framework, so that we can use a single name for the intended function of the form. This is the difference between

    Toggle line numbers
       1  from core.UI.cocoa import TitleEditForm
    
    and
    Toggle line numbers
       1  from core.UI.cocoa import TitleEditCocoaForm
    
    which is slightly redundant/un-DRY/un-Pythonic and which would make for greater rigidity in terms of how these forms are used at runtime. We of course do not expect to support all of these frameworks immediately, if ever.
  5. Interface object is the interface object the class represents. This is most likely a Form, but might also be a:

    • Wizard (for a progression of Forms in sequence)
    • CLI (command line interface — perhaps an ncurses-based "form")

    • ...
  6. An exception to this naming convention occurs when multiple parts of the class name are "Abstract". In that case, all but the first "Abstract" is dropped from the name. Thus,

     AbstractEditAbstractForm
    would be the expected name for a class which provides functions common to editing object of all models in all frameworks and toolkits. In this case, for readability, the name would be shortened to:
     AbstractEditForm
    Going up one more level,
     AbstractAbstractAbstractForm
    would be the expected name for a class which provides functions common to all Forms, regardless of model, function and framework; this would be shortened to
     AbstractForm

 

Class Relationships

 

Basic single inheritance relationships

The following inheritance pattern suggests itself, given the naming conventions discussed above. (Inheritance is indicated by indentation.)

AbstractForm
    AbstractEditForm
        TitleEditAbstractForm
            TitleEditWxForm
            TitleEditDjangoForm
            ...
        ContactEditAbstractForm
            ContactEditWxForm
            ...
        ...
    AbstractDisplayForm
        TitleDisplayAbstractForm
            TitleDisplayWxForm
            TitleDisplayDjangoForm
            ...
        ...

It is obvious that this tree could, theoretically, get quite complex — and this is to say nothing of multiple inheritance, as there necessarily would be in at least some cases (for example, a TitleEditCocoaForm would also need to inherit from the Cocoa framework's NSObject base class). This complexity is the price of abstraction. It may not always be strictly necessary to have every level of this tree — for example, it's hard for me to envision what functions would belong to the TitleEditAbstractForm class that wouldn't be better placed in the AbstractEditForm class — but this pattern anticipates that there may be such functions. Discussion on this topic is welcome.

 

"Flatter" multiple inheritance relationships

An alternative pattern also suggests itself which would somewhat simplify the inheritance relationships: thinking of each of the "abstract" class levels as a mixin class, and then multiply-inheriting the concrete classes from them. In this pattern, a TitleEditCocoaForm would inherit from, say, the three abstract classes TitleForm , EditForm , and CocoaForm . (Admittedly, these classes do not obey the conventions discussed above, but I think it might make sense to remain flexible as to how abstract classes fit into that convention.) Under this picture, the inheritance tree is much "flatter," and in a sense easier to understand, because classes are unlikely to use any attributes or methods which are not defined by their immediate parents. On the other hand, its sibling relationships are more complex; it's a matter of taste, I suppose, which pattern is preferable (though the first pattern is certainly easier to illustrate!):

 

AbstractForm
    TitleForm, EditForm, WxForm
        TitleEditWxForm
    TitleForm, EditForm, GTKForm
        TitleEditGTKForm
    ...
    ContactForm, EditForm, WxForm
        ContactEditWxForm
    ...

 

Execution Relationships and APIs

The application and data tiers should be indifferent to the particular class which is controlling the user interface, both when processing user input and when passing data back to display to the user. This means that there should be a common interface between the presentation and application tiers which any new presentation-level class should implement. We also may want to allow for the possibility that the user interface will be heterogenous with respect to the framework or toolkit that it uses: for example, it may use both GUI-based and HTML-based forms, or it may fall back to a TkInter-based form in a particular instance if no platform-native form has been written yet. This means that there should also be a common interface between forms within the presentation tier, not just between the particular segment of the presentation tier that's running and the application tier. That is:

  1. it should be possible for the application tier to communicate with any user interface object without knowing what framework it was written for; and
  2. it should be possible (though not necessarily desirable) for a user interface object in one framework to communicate with a user interface object from another available framework

The definition of these interfaces must wait on the selection of an inheritance pattern (see above). Therefore the discussion here remains open.

 

Code Organization

Assuming a Django-based project, then each Django "app" (i.e., collection of models and views) should also have a "UI" package which organizes itself according to the following Python namespace:

 

App/
    models/
    views/
    UI/
        __init__.py
        base.py
        html/
        tkinter/
        wx/
        cocoa/
        ...

That is, within the UI package for a given Django app, there should be a base module defining common functions, abstract classes, etc., and a sub-package for each of the supported frameworks.

My initial thought is that the default UI for a given installation should be defined in the settings module, and that the UI package's inititialization module for the given app should take care of looking up the default UI and exposing the correct class to the application tier. Thus, in UI/__init__.py we would see something like this:

Toggle line numbers
   1 from django.conf import settings
   2 
   3 # Import the correct UI class based on the default UI setting:
   4 if settings.DEFAULT_UI is 'html':
   5     from html import TitleEditHTMLForm as TitleEditForm
   6     from html import ContactEditHTMLForm as ContactEditForm
   7     # ...
   8 elif settings.DEFAULT_UI is 'wx':
   9     from wx import TitleEditWxForm as TitleEditForm
  10     # ...
  11 # ...
  12 

Then, in application-tier code, communicating with the current installation's UI is as easy as:

Toggle line numbers
   1 from app.UI import TitleEditForm
   2 
   3 # Do stuff with the TitleEditForm here...
   4