Descriptions as Patterns

Back to index


ABOUT PATTERN DESCRIPTIONS

Pattern descriptions describe software abstractions in bite-sized pieces, summarising a problem in context, the solution and its trade-offs. There is a strong movement identifying patterns in software development. I have tried to use Pattern Descriptions (this is my first attempt) in some of the framework documentation. Please provide feedback on whether this helps explain the framework better than just a plain class reference and examples.

To learn more about patterns join the mailing list or look at some of their papers:

Join mailing list by mailing a request to patterns-request@cs.uiuc.edu with SUBSCRIBE in the body of the message.

The mailing list and many academic papers are archived at:
(anonymous ftp to st.cs.uiuc.edu in /pub/patterns)

NOTE AND WARNING

The following is VERY rough, following a posting I made to the patterns network, and an attempt to formalise some of the OOFILE patterns. The first section, describing OOFILE in terms of the GOF patterns, will be reviewed and completed. I'm not sure how many other patterns will fall out of reviewing OOFILE and, if you are into patterns, would welcome comment on my attempts.

CLASSIC "GOF" (Gamma, Helm, Johnson & Vlissides) PATTERNS

In OOFILE,
our abstract classes used to talk to the database could best be described as Bridge pattern - the backend that does the work has a totally different interface.

The hierarchy looks like:

dbTable ------>  OOF_tableBackend  (abstract)
| |
| |
V V
User-defined OOF_tableBackend_ctree (concrete backend)

However, all data storage interaction is via descendants of dbField, which also point to the abstract backend.

So, I guess the catalog of patterns here would be:
- Bridge (dbTable talking to OOF_tableBackend)
- Proxy (OOF_tableBackend as interface to OOF_tableBackend_ctree)
- Mediator? (a dbTable includes a set of attached dbFields. They talk to the abstract backend to get their data. However, actions on the dbTable will affect them as a side effect, eg: moving to another record invalidates them. The side effects are all transferred through the backend - it's invalidated, so I think it could be regarded as a Mediator).

Other patterns used:
- Prototype (copying a dbTable copies its dynamic field dictionary)
- Abstract Factory (a database connection creates the backends depending on its type).
- Decorator (attaching related tables at runtime)
- Iterator (dbTable iterating over records in a set)


HOME-GROWN PATTERNS IN OOFILE

Pattern 1) Name: Associating an object with a "blind" parent.

Problem:
Declaration of objects which are "owned" by a parent object, with minimal public interface. We don't want to have to pass the parent object around as a parameter to constructors, and may indeed be unable to do so (eg: c++ arrays which invoke default constructors).

Context:
c++ or other languages which allow class members to be accessed from outside the class, preferably under controlled access.

Forces:
F1 - Keep public interface simple ã can't pass parent as parameter
F2 - avoid global variables
F3 - minimise knowledge stored between classes - transient linking to parent during construction phase is preferable to a permanent attribute

Solution:
You can make use of a static class member as a "safe" global variable, and rely on initialisation order. The parent object has a static member which is initialised to point to itself during construction:

sCurrentlyConstructing = this;

The default constructor for the owned dbTable objects then invokes this static member:

dbConnect::sCurrentlyConstructing->AttachTable(this);

Using c++ access control, the currentlyConstructing member could be made private and thus only accessible by the dbTable constructor. This prevents abuse by other functions.

Forces Resolution:
F1 - no parameters required due to class static member with temporary validity

F2 - no globals used - static class member doesn't pollute the namespace

F3 - declaring the child class constructors as friends of the parent, instead of the entire child class, the only time the currentlyConstructing member is accessed is during child construction.

F3 - set the currentlyConstructing pointer to nil after all possible clients have called it, thus blocking misuse of the pointer later.

Pattern 2) Name: Associating fields with tables by declaration.

Problem:
Declaration of database tables, including specifying database fields with some attributes (eg: text size). We want as simple an interface as possible so that table class declarations are very readable, amounting to little more than just a declaration of fields. This will appeal to programmers migrating from other databases.

Context:
OOFILE database declarations within a c++ program.

Forces:
F1- Declarations should be simple for readability of large schema
F2- c++ initialization order - base class, members, class constructor body
F3- Declaration effects should be reproducible dynamically - can't use a preprocessor for important behaviour
F4- some table members may not be database fields - can't assume all

Solution:
An object (eg: a dbConnect_ctree) of a backend-specific subclass of dbConnect is defined before any tables, implying that tables then defined belong to that connection,

dbTables are attached to the dbConnect using Pattern 1) Associating an object with a "blind" parent.

Each user-defined dbTable subclass contains a number of dbField objects. The dbField constructors are automatically invoked after the dbTable base class constructor, so pattern 1) applies again in attaching dbFields to the current dbTable.

Any dbField constructors which require info (eg: string lengths) must be explicitly listed in an initialisation list of the dbTable constructor, eg:
dbPeople() : LastName(40, kIndexed), OtherNames(40), Phone(13) {};

Note: for readability, some users have requested a variation on this, where methods are called within the constructor, rather than a complex initialisation list:

dbPeople() : LastName(40), OtherNames(40), Phone(13) {
LastName.Indexed();
};

Forces Resolution:
F1- Declarations are minimal, all logic is hidden in the base class constructors.

F2 - c++ init order is turned into a way of hiding complexity, using the base and field constructors to anonymously attach fields.

F3 - as all logic is embedded in class constructors, a dynamic schema can be generated just by creating objects in sequence - dbConnect, dbTable, dbFields.

F3 - preprocessor usage to declare a number of operators returning the dbTable subclass is OK if that is just to satisfy the compiler type-checking and these operators just inline to the calls used to implement operations in a dynamically created database. eg:
dbPeople& operator[](const dbQueryClause& rhs) {
dbTable::Search(rhs);
return *this;
};

F4 - database presence is established by explicit actions of the constructors of dbField subclasses. Therefore dbTable members that are not dbField descendants will not be linked to the database in any way.

Pattern 3) Name: Linking Relations between Actual table Objects.

Problem:
Most of the database effects are accomplished by the base class and dbField descendant member constructors. Thus, we just declare a database and the instantiation of the database into connection and table objects invokes the appropriate methods. However, for relations, we need a two-way link between actual object.

Context:
Establishing pointers to database objects to manage relations.

Forces:
F1 - Need pointers to dbTable objects so c++ syntax can be used to access & search on fields in related tables.

F2 - Can't instantiate link between objects until both defined.

F3 - A database may contain several tables of the same class, with need to distinguish links.

F4 - Relations may be of different types, user-defined or with other restrictions. There needs to be a point of defining relation type.

Solution:
Members for relations are declared as pointers to classes, which may be forward declared. After definition of the table variables, links are explicitly specified:
People.Visits = Visits.Inverse(Visits.Person);
Visits.Person = People.Inverse(Visits.Person);

Forces Resolution:
F1 - use pointers to classes for all relations regardless of type

F2 - Relation explicitly specified after definition of all class variables

F3 - Explicit definition of relation avoids ambiguity if multiple tables of same class. Note that there may be other issues for multiple tables related to same.

F4 - Methods such as ValueJoin(), or RelateOverField() can be added in an assignment of related table, to customise the relation.

Pattern Name:

Problem:

Context:

Forces:

Solution:

Forces Resolution:


Back to index