Lexi, or the Quest for the Mythical Editor - Felix John COLIBRI. |
- abstract: An introduction to design patterns by coding the Lexi document editor
- key words: design patterns - Lexi - document editor - gof - gang of four -
Composite - Decorator - Strategy - Iterator - Visitor - Command - Abstract Factory - Bridge
- software used: Windows XP, Delphi 6
- hardware used: Pentium 1.400Mhz, 256 M Ram, 140 G disk
- scope: Delphi, Java, C#, C++ programmer
- level: Intermediate
- plan:
1 - Introduction Who's Lexi ?
Lexi is the name of the sample "document editor" used by the Gang of Four, Gof for short, as the introductory example to design patterns. This book written in 1996 by Erich GAMMA, Richard HELM,
Ralph JOHNSON, John VLISSIDES literaly started the whole pattern movement. After introducing the Design Pattern idea, the Gof spent about 40 pages presenting a case study which explains how to use 8 typical patterns in the
course of designing the "LEXI document editor". The next 300 pages then explain each of the 23 Design Patterns, with motivation, diagrams and coding examples. However the book does not present the full source code of Lexi. In addition,
the other coding examples are in other domains than document editing, like game programming for instance. I have nothing against enchanted rooms, exploding doors or flying windows, but I missed the more realistic text editor example.
Googling on the web, you will find lots of "editors", and in source. To name a few: - HotDraw and jHotDraw
- Interview and UniDraw, which are editor frameworks
- MiniDraw as example for learning object oriented programming
- figure editor, mainly used as testbeds for Aspect Oriented Programming
The Gof tell us in a note that Lexi is based on "Doc, a text editing application developed by Calder". But this paper only outlines an editor,
without any source. And I even believe today that Lexi never truly existed as a program. Well, no longer: here comes Lexi ! In writing my own Lexi, my purpose was: - to follow as closely as possible the Gof case study
- to offer a Delphi source code, that you can compile, execute, examine, improve
2 - The description of Lexi 2.1 - The Original Lexi
The Gof book presents the following Lexi example:
2.2 - Our specification The editor we wish to build will have the following features: - characters, shapes, bitmaps
- layout in lines and columns
- toolbar for character, shape, bitmap
- mouse selection of an area for font change
- caret positioning for insert, delete
- infinite undo
- reformatting
- orthographic correction
- optional scrollbars and borders
- use of different windowing libraries
This is not a bona fide specification, but will be sufficient to give you an idea of the ground that will be covered.
The user interface will have the following look:
2.3 - The Classes In an analysis and design paper, we would show how to go through the user
description, build the specification, perform a full fledged analysis and design phase, to finally find the classes and their responsibilities (attributes and methods). Our goal however is to stress the Design Pattern point of view. Patterns are
one level above Classes: they are concerned with Classes relations and interactions. We will start with the following obvious Classes, which would be defined by any programmer having some Object Oriented programming experience:
- c_character, c_rectangle, c_bitmap to handle the elements of our editor
- c_row and c_page for the structure
- c_caret, c_selection for the mouse handling
So let us now dwelve into the Pattern part.
3 - The Composite Pattern 3.1 - The structure The most important decision is the structure for storing the items in memory.
All processing will manipulate in one way or another this structure. Our data consist of pages / columns / rows / items. To any programmer worth his salt, this should be organized as a tree-like structure. It could be
constructed with binary trees, n-ary trees, lists of lists, or any other parts / whole structuring element of the programming language.
Many of the function on each figure element like drawing, loading, saving, will also have their counterpart on the structure elements like rows, columns and pages. Hence the idea to use a common abstract ancestor, with all the
commonalities, and both individual pieces AND the structural parts will descend from this ancestor. In UML notation, we will have the following organization: In this figure
- the c_glyph is the abstract class for all drawing (pieces and structure). This class is ABSTRACT, as testified by the italic font of the class name
- the c_character, c_rectangle, c_bitmap are some of the possible elements of the document
- c_page, c_column (which we did not implement) and c_row are the structuring parts
In Delphi terms, the CLASS definitions will be the following:
The benefit of using the same ancestor for the element AND for the structure is that we will be able to call a single function for drawing the whole structure: - here is the individual c_character draw method:
procedure c_character_glyph.draw(p_c_client_region_drawing: c_client_region_drawing);
begin with p_c_client_region_drawing do
draw_text(m_x, m_y, m_name[1]);
end; // draw | - the c_composite_glyph simply calls the drawing of each individual child glyph:
procedure c_composite_glyph.draw(p_c_client_region_drawing: c_client_region_drawing);
var l_glyph_index: Integer; begin
for l_glyph_index:= 0 to f_glyph_count- 1 do
f_c_glyph(l_glyph_index).draw(p_c_client_region_drawing);
end; // draw | - and, to draw the whole figure with all rows and characters, the main program calls:
VAR g_c_page_glyph: c_page_glyph;
g_c_client_region_drawing: c_client_region_drawing; ...
g_c_page_glyph.draw(g_c_client_region_drawing); |
3.2 - The evolution of Trees
Way back on the Apple ][, we used pointers to build trees. Wirth's "Algorithms+ Data Structures" was the golden book in those time (it still should be for starting programming), and we represented trees in the following way:
with the corresponding Pascal definitions and declarations:
TYPE t_pt_cell= ^t_cell; t_cell= RECORD
m_pt_lower: t_pt_cell;
m_name: String;
m_pt_greater: t_pt_cell;
END;
VAR g_pt_root: t_pt_cell;
PROCEDURE add_cell(p_name: String);
PROCEDURE list_names_recursive(p_pt_cell: t_pt_cell); | Notice that:
- there is no distinction between the "tree" and the "leaves".
- all processing is done using procedures, which manipulate the global variable (the structure), usually recursing to get at the leaves (the elements)
If we wanted to handle several trees in our programs, we used parameters to pass the different trees to the procedures. And when the program grew in size and complexity, we placed the tree handling in a UNIT, thereby building an
Abstract Data Type. The starting point of the Object movement.
Now comes step two: Bertrand MEYER explained in his seminal book on Object that there are processing concerning the leaves
(displaying its shape, saving it on disk), and some processing concerning the whole structure (counting the elements, rearranging the layout). So it was rightly advised to separate the data and the functions in two CLASSes:
- on class representing the structure elements
- one class reprsenting the structure itself
The definition therefore looked like this: with the following code:
TYPE c_cell= CLASS
m_c_lower: c_cell;
m_name: String;
m_c_greater: c_cell;
PROCEDURE display_cell;
END; c_tree= CLASS
m_c_root: c_cell;
PROCEDURE display_tree;
PROCEDURE count_cells;
END; |
Finally the Design Patterns recommend to derive the structure from an abstract
element, factoring out the functions common to both the element and the structure: Somehow we are back to step one, with the element as the top of the handling.
However this "element" is an ABSTRACT element, nowhere to be instantiated: it is defined only to offer a unified syntax and allow a single call from the user program.
But this has far reaching consequences as well. With this recursive structure, we can build trees of trees. Let us compare the classical tree CLASSes to the "pattern" tree CLASSes:
In the "pattern" architecture, c_tree is a child of c_cell, and thererfore can be used in the same way as any other c_cell. Hence the trees of trees:
3.3 - UML This also shows the power of object notation: just looking at the class diagram, you can foresee the consequences, ponder the alternatives, evaluate
the costs and benefits of each organization. UML comes here very handy: it unified the notation. That's the least one could expect from a UNIFIED thing. I am not overly enthusiastic about the whole UML
business though. As criticized by many people already, the whole set is too redundant, trying to include every little bit of notation to make sure that no single searcher would start promoting his own drawings. Grady BOOCH's touch, I
guess. Reminds me of the early eighties, when Bill Gates was trying to propose "standards" everywhere, his standards of course. Anyway, tools that require such huge books to describe them cannot be that perfect. For me, three quarter
of the graphics can be thrown to the thrash can right from the start. As explained by Bertrand MEYER, Use Cases are even a big step backward. To me, Use Cases often take the form of "I get up in the morning, and
then I shave, and then I brush my teeth, and then I have breakfast...". Simple flows of actions, like in the olden time of structured programming. This looks more like talking to a 6 year old kid than Analysis and Design. Where are the
concepts, where are the abstractions ? So, in this paper, I will only use Class Diagrams, Object Diagrams, eventually Sequence diagrams. In my own projects, I sometimes also use State Charts, when
the application requires the representation of complex interactions. And that's it.
4 - The Decorator Pattern 4.1 - Objective
This is an easy one. The objective is to let the user add scroll bars or borders to his page:
In addition: - we do not want to change any existing source line: the client program uses
the same calls to perform drawing, saving etc, whether the structure is nested in scrolling or border containers or not
- the addition of "decorations" will take place at RUN TIME
This is achieved:
- by creating a new class with the bordering artifact
- this class includes a attributes which points to the existing page
- the new class
- directly handles all the border management
- forwards all calls to the existing page
If we use several kinds of border, we define an abstract parent, and can instantiate any of the children at run time.
The UML representation is the following: And since the border and the page are both descendents of c_glyph, we can nest the addition of decoration at any depth.
4.2 - Our implementation There is our Delphi code: - the abstract decorator:
c_abstract_border_decorator= class(c_page_glyph)
_m_c_enclosed_glyph: c_page_glyph;
Constructor create_abstract_border_decorator(p_name: String;
p_c_parent_glyph: c_glyph;
p_c_enclosed_glyph: c_page_glyph);
procedure draw(p_c_client_region_drawing: c_client_region_drawing); Override;
// -- replace ALL the functions called for m_c_root_page
procedure append_glyph(p_c_glyph: c_glyph); Override;
procedure select_in_rectangle(p_absolute_rectangle: tRect); Override;
// -- ... ALL the other
end; // c_abstract_border_decorator
| - with the following implementation code:
Constructor c_abstract_border_decorator.create_abstract_border_decorator(p_name: String;
p_c_parent_glyph: c_glyph; p_c_enclosed_glyph: c_page_glyph);
begin // -- initialize with the enclosing rectangle of the previous enclosed
with p_c_enclosed_glyph do
Inherited create_page_glyph(p_name, p_c_parent_glyph,
f_absolute_x, f_absolute_y, m_width_max, m_height_max);
_m_c_enclosed_glyph:= p_c_enclosed_glyph;
end; // create_abstract_border_decorator // -- delegation of the methods
procedure c_abstract_border_decorator.draw(p_c_client_region_drawing: c_client_region_drawing);
begin _m_c_enclosed_glyph.draw(p_c_client_region_drawing);
end; // draw
procedure c_abstract_border_decorator.append_glyph(p_c_glyph: c_glyph);
begin _m_c_enclosed_glyph.append_glyph(p_c_glyph);
end; // append_glyph | - and the "frame" class:
c_frame_decorator= class(c_abstract_border_decorator)
Constructor create_frame_decorator(p_name: String;
p_c_parent_glyph: c_glyph;
p_c_enclosed_glyph: c_page_glyph);
procedure draw(p_c_client_region_drawing: c_client_region_drawing); Override;
end; // c_frame_decorator |
- with the corresponding code:
procedure c_frame_decorator.draw(p_c_client_region_drawing: c_client_region_drawing);
begin // -- draw the border
with p_c_client_region_drawing do
draw_rectangle(m_x, m_y, m_width_max, m_height_max);
// -- draw the enclosed content Inherited;
end; // draw |
Here is a shapshot of the addition of a couple of frames (in green) and scrollbars (in blue):
4.3 - CLASSes vs COMPONENTS The key to the decorator pattern is delegation. The scroller delgates all page method calls to the enclosed page.
Delegation was already at the heart of the COM concept, where COM objects could encapsulate other COM objets, provided that the calls to the inner object were forwarded by the wrapper:
We can go one step further: today, many people talk about "components" as software pieces that the USER can assemble at runtime. We have been accustomed
to talk about Delphi components, which are assembled at design time. In both cases, to build a working application, on needs to glue the components together so that they can work together. The gluing part is performed by so called "connectors"
Some people then criticized the Delphi framework because - it only assembles graphical components (which is not entirely true)
- much of the time spent by a developer is in writing glue code, instead of using more finished components and let the user snap them together.
I was very disappointed. I truly believed that during all those years I had
been using Delphi to write wonderful code with cute algorithms, whereas I was merely gluing things together. And they even tell me that I was using the wrong glue !
In any case, the component / connector / composition / delegation issue is at the heart of intense research today. And also notice that the delegation we are talking about is not the "Delphi
delegation", where a tButton "delegates" the OnClick to the tForm CLASS:
5 - The Iterator Pattern 5.1 - Iterating over the elements In many places we will have to enumerate the items of our structure. Our
prefered method was to recurse in the tree structure. To check whether our page contains any rectangle, we would use:
function c_page_glyph.f_contains_rectangle_glyph: Boolean;
procedure check_recursive(p_c_glyph: c_glyph);
var l_glyph_index: Integer; begin
if p_c_glyph is c_rectangle_glyph
then begin
Result:= True;
Exit; end;
if p_c_glyph is c_composite_glyph
then
with c_composite_glyph(p_c_glyph) do
for l_glyph_index:= 0 to f_glyph_count- 1 do
check_recursive(f_c_glyph(l_glyph_index));
end; // check_recursive begin // f_contains_rectangle_glyph
Result:= False; check_recursive(Self);
end; // f_contains_rectangle_glyph |
This code requires intimate knowledge of the structure implementation. A more
transparent way would be to simply call some "next" enumerator which would hand over all the elements required by our computation:
function c_page_glyph.f_contains_rectangle_2: Boolean;
// -- an iterator exercise
var l_c_leaf_iterator: c_leaf_iterator; begin
l_c_leaf_iterator:= c_leaf_iterator.create_leaf_iterator('leaf_iter', Self);
l_c_leaf_iterator.goto_first_glyph; Result:= False;
while not l_c_leaf_iterator.f_is_done do
begin
if l_c_leaf_iterator.f_c_current_glyph IS c_rectangle
then begin
Result:= True;
Break; end;
l_c_leaf_iterator.goto_next_glyph;
end; // while not f_is_done l_c_leaf_iterator.Free;
end; // f_contains_rectangle_2 |
5.2 - The Iterator Pattern
Basically the iterator pattern has the following methods - goto_first_glyph
- f_c_current_glyph
- goto_next_glyph
- f_is_done
and in addition it somehow remembers the current position to be able to fetch the next item. If our structure has multiple containers, implemented by different means (trees, lists, arrays, bags, collections), we will create an abstract iterator,
and derive the tree iterator, list iterator etc from this ancestor. The client code would declare an abstract iterator variable, and instantiate whatever concrete iterator is desirable at run time. The UML schema for the iterator is:
Notice that - there is a link from the iterators to the c_glyph, since the iterator has some member attribute to remember the current position (this is not inheritance: it is a relation between two classes)
- the c_null_iterator is useful if the container structure is nested at the c_glyph level: any iterator at this level should always return f_is_done
TRUE. We nested the structure in c_composite_glyph, so the c_null_iterator is of no use.
- if we use a tree-like structure, we may build several iterators:
- iterators for pages
- iterators for rows
- iterators for characters
5.3 - The Delphi Implementation The abstract iterator is defined as:
c_abstract_iterator= class(c_basic_object)
_m_c_glyph_ref: c_glyph;
Constructor create_abstract_iterator(p_name: String;
p_c_glyph_ref: c_glyph);
procedure goto_first_glyph; Virtual; Abstract;
procedure goto_next_glyph; Virtual; Abstract;
function f_c_current_glyph: Virtual; Abstract;
function f_is_done: Boolean; Virtual; Abstract;
end; // c_abstract_iterator |
We used the following composite iterator class (to iterate over ANY composite):
c_composite_iterator= class(c_abstract_iterator)
_m_c_composite_glyph_ref: c_composite_glyph;
m_glyph_index: Integer;
Constructor create_composite_iterator(p_name: String;
p_c_glyph_ref: c_glyph);
procedure goto_first_glyph; Override;
procedure goto_next_glyph; Override;
function f_c_current_glyph: c_glyph; Override;
function f_is_done: Boolean; Override;
end; // c_composite_iterator |
with the following implementation:
Constructor c_composite_iterator.create_composite_iterator(p_name: String;
p_c_glyph_ref: c_glyph); begin
Inherited create_abstract_iterator(p_name, p_c_glyph_ref);
// -- avoid continuous casts
_m_c_composite_glyph_ref:= c_composite_glyph(_m_c_glyph_ref);
end; // create_composite_iterator
function c_composite_iterator.f_display_iterator: String;
begin Result:= m_name;
end; // f_display_iterator
procedure c_composite_iterator.goto_first_glyph; begin
m_glyph_index:= 0; end; // f_c_first_glyph
procedure c_composite_iterator.goto_next_glyph; begin
Inc(m_glyph_index); end; // f_c_next_glyph
function c_composite_iterator.f_is_done: Boolean; begin
Result:= m_glyph_index>= _m_c_composite_glyph_ref.f_glyph_count;
end; // f_is_done
function c_composite_iterator.f_c_current_glyph: c_glyph; begin
Result:= _m_c_composite_glyph_ref.f_c_glyph(m_glyph_index);
end; // f_c_current_glyph |
As an example of a simple iterator, the coloring and resizing of fonts use
iterators. Here is a snapshot:
Now the interesting part. The Gof explained that in order to iterate over the
leaves of the tree in "preorder" (the bottom leaves first), we should use a stack to save the nodes during the descent in the branches. This is classic tree traversal business: you can visit the nodes of a tree in depth first or
width first order (or in any other custom order you may choose): The tree shown above are "binary sort tree": the user entered "m" "e" "a" "z"
"d", and by visiting the node in preorder, you will see "a" "d" "e" "m" "z" (which is the alphabetical sort order). However this is not what we have in Lexi. Let us assume that we enter the following lines: me azd
If we build the tree by adding the rows at the bottom of the tree, which is the normal way of adding items to a tree (it always grows at the leaf level), the tree will look like this:
This can be more naturally presented by rotating the tree 45 degrees anti clock wise:
In this case the "preorder" iterator of the Gof will visit the character in the following order: azd me So the first line entered is visited last (this is depth first). If the
insertion order if of no importance to you, this is irrelevant. But for hyphenation, for instance, it is crucial that leaves are visited in the insertion order.
To get insertion order traversal, we can:
- build the tree by inserting new rows "above" previous rows
- build another visiting algorithm
- change the page / row / character structure
We chose to change the visiting algorithm, since other pieces of code assuming
bottom growth of the tree were already written. Here is the code:
procedure c_leaf_iterator.goto_first_glyph;
var l_c_composite_iterator: c_composite_iterator;
l_c_current_glyph: c_glyph;
l_c_child_iterator: c_composite_iterator; begin
l_c_composite_iterator:= c_composite_iterator.create_composite_iterator('compo', _m_c_glyph_ref);
l_c_composite_iterator.goto_first_glyph;
m_c_composite_iterator_stack.clear_stack;
m_c_composite_iterator_stack.push_composite_iterator(l_c_composite_iterator);
// -- even for the first, must drill down to get the first leaf
with m_c_composite_iterator_stack do begin
l_c_current_glyph:= f_c_top_of_stack_composite_iterator.f_c_current_glyph;
l_c_child_iterator:= c_composite_iterator.create_composite_iterator('child_iter', l_c_current_glyph);
l_c_child_iterator.goto_first_glyph;
push_composite_iterator(l_c_child_iterator); end;
end; // goto_first_glyph
procedure c_leaf_iterator.goto_next_glyph;
var l_c_current_glyph: c_glyph;
l_c_child_iterator: c_composite_iterator;
l_c_composite_iterator: c_composite_iterator; begin
with m_c_composite_iterator_stack do begin
l_c_current_glyph:= f_c_top_of_stack_composite_iterator.f_c_current_glyph;
if l_c_current_glyph is c_composite_glyph
then begin
l_c_child_iterator:= c_composite_iterator.create_composite_iterator('child_iter', l_c_current_glyph);
l_c_child_iterator.goto_first_glyph;
push_composite_iterator(l_c_child_iterator);
end else begin
// -- this is a simple glyph
Repeat
l_c_composite_iterator:= f_c_top_of_stack_composite_iterator;
// -- get the next (to toggle done eventually)
l_c_composite_iterator.goto_next_glyph;
if l_c_composite_iterator.f_is_done
then begin
// -- pop and free
f_c_pop_composite_iterator.Free;
l_c_composite_iterator:= Nil;
if not f_is_empty
then begin
l_c_composite_iterator:= f_c_top_of_stack_composite_iterator;
if not l_c_composite_iterator.f_is_done
then begin
l_c_composite_iterator.goto_next_glyph;
if not l_c_composite_iterator.f_is_done
then // -- recurse
Self.goto_next_glyph
else l_c_composite_iterator:= Nil;
end;
end;
end;
until f_is_empty or (l_c_composite_iterator<> Nil);
end; end; // with
end; // goto_next_glyph | We did not present the c_composite_iterator_stack which is a simple stack
build with a tList (see the .ZIP for the details).
You might believe that I am heavily criticizing the Gof Iterator. Quite the contrary: this is a perfect example of the usefulness of this pattern: instead
of smearing the user code with different visiting algorithms, we encapsulate all the iteration part in the Iterator classes. Our Lexi code does not use the iterator everywhere, since I started the project
with standard recursion, before I reached the Iterator part. But those recursions could now be replaced with iterators everywhere (except in the iterators themselves).
6 - The Visitor Pattern 6.1 - Spell Checking Many functions will require visiting the characters of the text in insertion order. Spell checking is an example. We could add word counting (many magazine
pay authors based on word count), or text search, translation etc. If we have many different processing tasks, Gof recomends to use the Visitor pattern:
- we define an abstract visitor class, and separate descendent visitors, one for each specific task
- each part of our structure (c_glyp, c_row, c_page) has an
accept_visitor(c_abstract_visitor) method
- the client code
- initializes a concrete Visitor
- uses an Iterator to reach the elements
- for each element, accept_visitor is called, and the specific visitor handles the task
In our spell checking case: - the main application will create a c_spell_checker visitor
- a c_character_iterator will find each character
- for each character, the c_spell_checker will be called:
- if the character is a letter, it will be concatenated to a "running word"
- if the character is a separator (space, return...), the running word will be checked against a dictionnary
And if we wish to do word counting, the only thing that changes is the
instantiation of a c_word_count_visitor instead of a c_spell_checker_visitor.
6.2 - Visitor UML representation
Notice that there is a mutual link between c_glyph and c_visitor (since c_glyph uses c_visitor as a parameter in accept_visitor, and each c_visitor
uses a c_glyph descendent in the visit_xxx methods)
6.3 - The Delphi implementation Here is the c_abstract_visitor:
c_abstract_visitor= class(c_basic_object)
procedure visit_character(p_c_character_glyph: c_character_glyph); Virtual; Abstract;
procedure visit_row(p_c_row_glyph: c_row_glyph); Virtual; Abstract;
end; // c_abstract_visitor |
The spell checker is defined as:
c_simple_spelling_checker_visitor= class(c_abstract_visitor)
m_word_to_check: String;
Constructor create_simle_spelling_checker_visitor(p_name: String);
procedure visit_character(p_c_character_glyph: c_character_glyph); Override;
end; // c_spelling_checker_visitor
| and:
procedure c_spelling_checker_visitor.visit_character(p_c_character_glyph: c_character_glyph);
var l_character: Char; begin
l_character:= p_c_character_glyph.m_name[1];
if l_character= ' '
then begin
// -- check the word
m_word_to_check:= ''; end;
else m_word_to_check:= m_word_to_check+ l_character;
end; // visit_character | with the following client code:
procedure c_page_glyph.check_spelling;
var l_c_leaf_iterator: c_leaf_iterator;
l_c_spelling_checker_visitor: c_simple_spelling_checker_visitor; begin
l_c_leaf_iterator:= c_leaf_iterator.create_leaf_iterator('leaf_iter', Self);
l_c_spelling_checker_visitor:=
c_simple_spelling_checker_visitor.create_simple_spelling_checker_visitor('spell');,
with l_c_leaf_iterator do begin
goto_first_glyph;
while not f_is_done do
begin
f_c_current_glyph.accept_visitor(l_c_spelling_checker_visitor);
goto_next_glyph; end; // while not f_is_done
l_c_previous_row.accept_visitor(l_c_spelling_checker_visitor);
Free; end; // with l_c_leaf_iterator
l_c_spelling_checker_visitor.Free; end; // check_spelling |
We assumed that words were separated by punctuations like spaces, commas, returns etc. In many editors, there are 2 line breaking possibilities: - the return entered by the user
- the end of line imposed by the line breaking algorithm. If the line is longer then allowed by the window size, the editor can use "pseudo returns" (some unused character replacing the last space, not shown to the user, and
marking the end of line for the line handling tasks).
If we do not use pseudo-returns (which is the unhappy choice we made in this version of Lexi), then previous visitor will mistakenly concatenate each end
of line words with the next start of line word. The character flow does not allow detection of the line break. One possibility would be to use the y value of each character. Another possibility is to use a row_visitor.
Here is the new visitor:
c_spelling_checker_visitor= class(c_abstract_visitor)
m_word_to_check: String;
Constructor create_spelling_checker_visitor(p_name: String);
procedure _check_the_word;
procedure visit_character(p_c_character_glyph: c_character_glyph); Override;
procedure visit_row(p_c_row_glyph: c_row_glyph); Override;
end; // c_spelling_checker_visitor
| which is implemented as:
procedure c_spelling_checker_visitor._check_the_word; begin
if m_word_to_check<> ''
then begin
// -- do the checking
m_word_to_check:= ''; end;
end; // _check_the_word
procedure c_spelling_checker_visitor.visit_character(p_c_character_glyph: c_character_glyph);
var l_character: Char; begin
l_character:= p_c_character_glyph.m_name[1];
if l_character= ' '
then _check_the_word
else m_word_to_check:= m_word_to_check+ l_character;
end; // visit_character
procedure c_spelling_checker_visitor.visit_row(p_c_row_glyph: c_row_glyph);
begin _check_the_word; end; // visit_row |
and is used like this:
procedure c_page_glyph.check_spelling;
var l_c_leaf_iterator: c_leaf_iterator;
l_c_current_glyph: c_glyph;
l_c_spelling_checker_visitor: c_spelling_checker_visitor;
l_c_current_row, l_c_previous_row: c_row_glyph; begin
l_c_leaf_iterator:= c_leaf_iterator.create_leaf_iterator('leaf_iter', Self);
l_c_spelling_checker_visitor:=
c_spelling_checker_visitor.create_spelling_checker_visitor('spell');
l_c_previous_row:= Nil;
with l_c_leaf_iterator do begin
goto_first_glyph;
while not f_is_done do
begin l_c_current_glyph:= f_c_current_glyph;
if l_c_current_glyph is c_character_glyph
then begin
l_c_current_row:= l_c_current_glyph.f_c_parent_glyph as c_row_glyph;
if (l_c_previous_row<> Nil) and (l_c_current_row<> l_c_previous_row)
then l_c_previous_row.accept_visitor(l_c_spelling_checker_visitor);
l_c_previous_row:= l_c_current_row;
end;
l_c_current_glyph.accept_visitor(l_c_spelling_checker_visitor);
goto_next_glyph; end; // while not f_is_done
l_c_previous_row.accept_visitor(l_c_spelling_checker_visitor);
Free; end; // with l_c_leaf_iterator
l_c_spelling_checker_visitor.Free; end; // check_spelling |
6.4 - Visitors Visitors are a very nice pattern, which helps us add all kinds of functionality to our application. In the following figure, we represented the structure, where each element has a
reference to the abstract visitor (the white ellipse): When the client instantiates a visitor descendent (the blue visitor, for
instance), the iterator will go through the elements of the structure, and call the "blue" visitor processing: To add a new kind of processing, all we have to do is
- to write a new c_abstract_visitor descendent
- in the client code, instantiate THIS visitor
On the donwside however, whenever we add a new c_xxx_glyph element type, we
will have to add a new visit_xxx_glyph method in each c_visitor. For instance, adding a new c_column_glyph forces us to add visit_column in the c_abstract_visitor, and in all its descendents:
6.5 - Main Lesson From the Gof I think by now you will start to get some feel of the pattern way of coding. Essentially, whenever your application uses features with "some variability"
- you encapsulate the feature in an abstract class
- the client references the abstract class
- you define the different processing in the descendent
- the client instantiates any of the descendents at run time
We already encountered this behaviour in Decorator, Iterator and Visitor. And it will be uses in many other patterns.
7 - The Strategy Pattern 7.1 - Formating Text
As another example of the "encapsulate variation" mechanism, let us look at the Strategy pattern. To display our Lexi text, we want to select one of several layout possibilities: justify to the right, to the left or on both sides.
The traditional solution is to write c_row.justify_left, c_row.justify_right, c_row.justify_both. If we want to define another layout, this would involve adding another method, AND calling it from the main
structure. The idea of the Strategy pattern is: - to define an abstract strategy
- different line breaking algorithms are implemented in descendents of the abstract strategy
- the client structure needing some reformatting instantiates one of the possible concrete strategies and calls it's justify method
7.2 - UML representation of Strategy The UML class diagram is the following:
7.3 - Delphi implementation Let us start with a simple "per line" justification: - the abstract strategy is defined by:
c_abstract_row_justification_strategy
= class(c_basic_object)
m_c_row_glyph_ref: c_row_glyph_ref;
procedure set_row(p_c_row_glyph_ref: tObject);
procedure justify_row(p_width: Integer); Virtual; Abstract;
end; // c_abstract_row_justification_strategy |
- here is the right justification class (the left justification is trivial, as the .ZIP will testify)
c_right_row_justification_strategy = class(c_abstract_row_justification_strategy)
Constructor create_right_row_justification_strategy(p_name: String);
procedure justify_row(p_width: Integer); Override;
end; // c_right_row_justification_strategy | with the following implementation:
procedure c_right_row_justification_strategy.justify_row(p_width: Integer);
// -- push everything to the right
function f_compute_row_minimum_width: Integer;
var l_c_character_iterator: c_composite_iterator; begin
l_c_character_iterator:= c_composite_iterator.create_composite_iterator('row_iter',
c_glyph(m_c_row_glyph_ref));
// -- first compute the pixels of the glyphs Result:= 0;
while not l_c_character_iterator.f_is_done do
begin
with l_c_character_iterator.f_c_current_glyph do
Inc(Result, m_width);
l_c_character_iterator.goto_next_glyph;
end; // while not l_c_character_iterator.f_is_done
end; // f_compute_row_minimum_width
procedure push_to_the_right(p_row_minimum_width: Integer);
var l_c_character_iterator: c_composite_iterator;
l_c_glyph: c_glyph;
l_running_x: Integer; begin
if p_row_minimum_width> p_width
then begin
display('*** not_wide_enough');
Exit; end;
l_c_character_iterator:= c_composite_iterator.create_composite_iterator('row_iter',
c_glyph(m_c_row_glyph_ref));
l_running_x:= p_width- p_row_minimum_width;
while not l_c_character_iterator.f_is_done do
begin
l_c_glyph:= l_c_character_iterator.f_c_current_glyph;
with l_c_glyph do
begin m_x:= l_running_x;
Inc(l_running_x, m_width);
end; // with l_c_row_glyph
l_c_character_iterator.goto_next_glyph;
end; // while not l_c_character_iterator.f_is_done
end; // push_to_the_right
var l_row_minimum_width: Integer;
begin // justify_row l_row_minimum_width:= f_compute_row_minimum_width;
push_to_the_right(l_row_minimum_width); end; // justify_row |
- and here is the client code:
procedure c_page_glyph._format_row(p_width: Integer;
p_c_row_justification_strategy: c_abstract_row_justification_strategy);
var l_c_row_iterator: c_composite_iterator;
l_c_row_glyph: c_row_glyph; begin
l_c_row_iterator:= c_composite_iterator.create_composite_iterator('row_iter', Self);
while not l_c_row_iterator.f_is_done do
begin
l_c_row_glyph:= c_row_glyph(l_c_row_iterator.f_c_current_glyph);
l_c_row_glyph.set_row_justification_strategy(p_c_row_justification_strategy);
p_c_row_justification_strategy.set_row(l_c_row_glyph);
with l_c_row_glyph do
p_c_row_justification_strategy.justify_row(p_width);
l_c_row_iterator.goto_next_glyph;
end; // while not l_c_row_iterator end; // _format_row
procedure c_page_glyph.format_row_to_the_left(p_width: Integer);
var l_c_row_justification_strategy: c_abstract_row_justification_strategy; begin
l_c_row_justification_strategy:= c_left_row_justification_strategy.create_left_row_justification_strategy('row_left');
_format_row(p_width, l_c_row_justification_strategy);
l_c_row_justification_strategy.Free; end; // format_row_to_the_left
procedure c_page_glyph.format_row_to_the_right(p_width: Integer);
var l_c_row_justification_strategy: c_abstract_row_justification_strategy; begin
l_c_row_justification_strategy:= c_right_row_justification_strategy.create_right_row_justification_strategy('row_right');
_format_row(p_width, l_c_row_justification_strategy);
l_c_row_justification_strategy.Free; end; // format_row_to_the_right
procedure c_page_glyph.format_row_both_sides(p_width: Integer);
var l_c_both_justification_strategy: c_both_row_justification_strategy; begin
l_c_both_justification_strategy:= c_both_row_justification_strategy.create_both_row_justification_strategy('both');
_format_row(p_width, l_c_both_justification_strategy);
l_c_both_justification_strategy.Free; end; // format_row_both_sides |
And by changing the row justification strategy constructor, it is easy to apply different justifications. And here we show the justification in action:
7.4 - Strategy and Composite Changing justification is easy to change, would'nt you agree ? Well, looking at the above code, it somehow smells:
- the concrete strategy needs some handle on the structure to do the computation
- the structure needs the correct strategy reference to apply the correct formating rule
Hence the mutual initialization:
l_c_row_glyph.set_row_justification_strategy(p_c_row_justification_strategy);
p_c_row_justification_strategy.set_row(l_c_row_glyph); | This is a Visitor in disguise !
That is not what the Gof tell us. Reading again and again those pages, my understanding is as follows: - the program receives a stream of characters
- the strategy is used to build the column / row structure with different line
breaking strategies.
And they stress the fact that the structure is not known at the client level: the client sends over the character stream, and the strategy transparently
builds the column / line structure (there is even a graphic, with shading and all, to hammer this point home). For the "first time" structure building, this would be all right with me. However the UML diagram presents the following code snippet:
Glyph.Insert(g, i);
Compositor.Compose(); | I understand this as - whenever the user insert a character at the caret position in a text
- insert the character in the tree structure and apply whatever formating strategy was chosen before the insertion
I fail to see how you can perform any decent formating without intimate
knowledge of the structure. If you add a single character to the end of the line, this would trigger the flushing of the complete word to the next line, with possible cascading adjustments up to the end of the paragraph. So you must
have a simple grip on the line structure. The same is true for deleting a character from the word at the start of a line: in some cases the word should be appended to the previous line, and the current line reformatted, with a
possible cascade reformatting.
In any case, I tried to implement this "no structure" formating. Whenever we call the formating, the complete text is formated according to a left / right
or "both side" strategy, and with reorganization of the line structure. The source of this "no structure" strategy is in the .ZIP. Nothing to be proud of: each time the formating method is called, a new page is rebuilt from scratch
using a glyph iterator, and, at the end of the procedure, this new page replaces the old page, which is then somehow destroyed.
Is this another insidious attack against the Gof ? Not at all. What I am
criticizing it the use of a "no structure strategy" pattern for reformatting text on the run. The Strategy pattern in itself is very useful, and the concept simple: encapsulate different computations in an abstract class, whose
descendents can be instantiated at run time. Should the Strategy have intimate knowledge of the structure it operates on: this would depend on the operation.
8 - The Command Pattern
8.1 - The Command CLASS This is yet another bright idea from Bertrand MEYER. When in a project you wish to have "undo" capability, you have to remember the changes, to be able to revert to the previous states. Thats obvious. So you
define: - a RECORD storing the "before" parameters
- a stack where the RECORDs are pushed, and eventually popped to undo the changes
Now if the changes come from different processings, you have to create a
different RECORDs for each kind of change. If you are in the Object Oriented Programming part since a couple of years, this "multiple record kind stack" is easily transformed in a list of OBJECTs,
using the polymorphic capability of the object oriented structures (tList, ARRAY or whatever). But back in 1985, when I first read about placing "Insert", "Append" and "Delete" in CLASSes, it certainly looked very strange.
Back to Lexi: to be able to have unlimited UNDO: - we create an abstract c_command CLASS
- each command that must have the UNDO possibility is specified by a derived
class, where the attributes save "enough information" to allow the undoing. For the insertion of a character, we saved the ASCII code, and the row / column indices.
- the action is performed
- by pushing the command object on a stack
- by doing the effective work (insert, delete etc)
8.2 - The Delphi Implementation We implemented the command pattern in the following way: - Here is the abstract command CLASS:
c_abstract_command= class(c_basic_object)
procedure execute_command; Virtual; Abstract;
function f_is_reversible: Boolean; Virtual; Abstract;
procedure undo_command; Virtual; Abstract;
end; // c_abstract_command |
- and the command stack:
c_command_stack= Class(c_basic_object)
m_c_command_stack: tStringList;
Constructor create_command_stack(p_name: String);
function f_command_count: Integer;
function f_c_command(p_command_index: Integer): c_abstract_command;
function f_is_empty: Boolean;
function f_c_push_command(p_c_command: c_abstract_command): c_abstract_command;
function f_c_peek_command: c_abstract_command;
function f_c_pop_command: c_abstract_command;
procedure undo_command;
Destructor Destroy; Override;
end; // c_command_stack |
- and we show the undo part:
procedure c_command_stack.undo_command;
var l_c_command: c_abstract_command; begin
if not f_is_empty
then begin
l_c_command:= f_c_pop_command;
l_c_command.undo_command;
l_c_command.Free; end;
end; // undo_command | - the concrete insert command is defined by:
c_insert_command= Class(c_abstract_command)
m_c_document_ref: c_document;
// -- m_name contains the name of the character (debug)
// -- this is the insertion point
m_row_index, m_column_index: Integer;
Constructor create_insert_command(p_name: String;
p_c_document_ref: c_document);
procedure execute_command; Override;
procedure undo_command; Override;
end; // c_command |
- and the insertion / undo methods
procedure c_insert_command.execute_command;
var l_row_index, lcolumn_index: Integer; begin
with m_c_document_ref do begin
f_c_insert_leaf_glyph(Self.m_name[1]);
// -- column_index-1 since by now the caret has been moved ahead
m_row_index:= m_c_caret.m_caret_row_index;
m_column_index:= m_c_caret.m_caret_element_index- 1;
end; // with m_c_document_ref end; // execute_command
procedure c_insert_command.undo_command;
var l_c_row_glyph: c_row_glyph; begin
with m_c_document_ref do begin
l_c_row_glyph:= m_c_root_page_glyph.f_c_glyph(m_row_index) as c_row_glyph;
l_c_row_glyph.remove_glyph_by_index(m_column_index);
m_c_root_page_glyph.compute_placement; clear_page;
draw_page_and_caret; end; // with m_c_document_ref
end; // undo_command | - the client triggers the insertion:
procedure c_document.do_insert_command(p_character: Char);
var l_c_insert_command: c_insert_command; begin
l_c_insert_command:= c_insert_command.create_insert_command(p_character, Self);
l_c_insert_command.execute_command; // -- add the command
m_c_command_stack.f_c_push_command(l_c_insert_command);
end; // do_insert_command | - to undo the insertion (or any other command), the client calls:
procedure c_document.undo_command_click;
begin m_c_command_stack.undo_command;
end; // undo_command_click |
Here is an example or typing "nice " on the keyboard, ad adding the "PC" bitmap
(the undo has not been shown, since it removes those):
9 - The Abstract Factory Pattern and the Bridge Pattern
Those two patterns deal with the creation and use of families of objects implemented with the help of two libraries. For instance Windows, X-Windows and Presentation Manager.
The main idea, once again, is to build a generic abstract layer, with the specific implementations derived from this abstract layer. We have implemented those patterns, and they are present in the .ZIP project.
But the full presentation of those patterns would burden this paper too much, so this has been presented in a companion paper.
10 - The Source code 10.1 - The file organization Our source code is organized in the following way ROOT
- p_lexi.dpr, u_lexi.pas, u_lexi.dfm: the project, the form
UNITS COMPOSITE
- u_c_glyph: c_glyph plus c_composite_glyph
- u_c_glyph_kinds.pas - u_c_page_glyph
- u_c_row_glyph DECORATOR
- u_c_glyph_border_decorator_2.pas
- u_c_glyph_border_decorator_kinds_2.pas ITERATOR
- u_c_iterator.pas
- u_c_iterator_kinds.pas VISITOR
- u_c_glyph_count_visitor.pas
- u_c_spelling_checker_visitor.pas
- u_c_visitor.pas STRATEGY
- u_c_row_justification.pas
- u_c_justification_strategy_2.pas BRIDGE
- u_c_bridge_canvas.pas
- u_c_bridge_drawings.pas COMMAND
- u_c_command.pas
- u_c_command_kinds.pas U_C_DOCUMENT
- u_c_document.pas U_C_CARET
- u_c_caret.pas U_C_SELECTION
- u_c_selection.pas FACTORY
- u_c_widget_factory.pas U_C_CONTROLS
- u_c_control.pas
- u_c_pen_brush_stack.pas
- u_c_round_controls.pas
- u_c_square_controls.pas
10.2 - Mini Guided Tour Should you wish to first run the project, here are some tips:
| select the "window implementation" by clicking "square_window_" or "round_window_" |
| create the Lexi application by clicking "create_" | |
the Lexi widget and the drawing surface will be displayed | | create some text by hitting either "generate_" or "random_" |
| the text will be displayed | |
try the justification by clicking on the "< justify", "justify >" etc widgets (the page width can be changed in the edit above the Lexi area) | |
select an area containing characters with the mouse, then change the font height by clicking the "+" or "-" widgets | |
position the caret, and type some text | | click the "rectangle_" or "bitmap_" widget, and type some text |
| click the "Esc" widget to undo the insertions | The "display_xxx" and "draw_xxx" buttons as well as the tMemo on the right are for debugging.
10.3 - Todo Many features have not been implemented or are only partially coded: - selection of the bitmap file name
- add "controls" for editing values (tEdit)
- implement the scrolling in the scroller Decorator
- change the font NAME (we only change the size)
- add the delete command (we have UNDO, but not DELETE)
10.4 - Known bugs
As explained, our goal was neither to write a perfect, or even a useable editor, nor to deliver bug free code. The patterns were our main focus. So the code still contains many bugs: - unimplemented features
- Paint is not considered at all.
- the mouse click assumes that you do not release the mouse outside of the widget where you started the click
- bugs. Among those I noticed while writing this paper:
- the layout of glyphs is not totally coherent (in some place we add spade between characters, in other they are glued together)
- justification does not always reset the layout, and currently only works on the "random" text
- the decorator pattern does not delegate enough of the enclosed glyph methods. This will have to be fixed in the next .ZIP release
11 - Lessons Learned 11.1 - Performance
- In my first trials, I had spontaneously placed the container structure (the tList) in a c_composite_glyph, instead of placing it at the c_glyph level.
The Gof Lexi presentation assumes that the composition takes place at the c_glyph level. But in the Composite chapter, they agree that the composition overhead (16 byte for a tList in our case) could be a
deterrent, and indicate that this load might be shifted to the c_composite_glyph, which is exactly what I did. They also tell us that doing so would complicate the code, since we would
have to make the distinction in attributes, ancestors and parameters between c_glyph and c_composite_glyph. - the Strategy pattern showed that using a single structuring concept (the
tree) for Lexi is not necessarily optimal.
In my opinion, whenever we have to reorganize the lines (hyphenation, cut and paste, duplication etc) the intimate knowledge of the structure would
actually simplify the code. If this kind of processing becomes important, the best structure could be an explicit page / column / row structure (page, column and row have their own container data and routines). We could then write:
FOR l_page_index:= 0 TO f_page_count- 1 DO
FOR l_column_index:= 0 TO f_column_count- 1 DO
FOR l_row_index:= 0 TO f_row_count- 1 DO
|
11.2 - The Delphi way
11.3 - Ambiguities The Lexi case study in the Gof book is not a specification. Presenting design
pattern in context was the main objective. This explains why in some places, there are some ambiguities concerning Lexi. For instance - it is not clear whether Lexi should be a "presentation" editor (some kind
of Page Maker) or a writing tool (the user inserts text by using a keyboard). This mainly showed up in the Strategy pattern, where handling the first-time formating is much easier than formating a line when a single
character at the end of the line could trigger massive reformatting
11.4 - The order of development This is simply some indication about how I developed Lexi. It is no way a
recommendation, and no doubt some people will criticize how this was done and the result of this effort. Well, lets see their source code and we will start talking about their techniques. In any case - the development took about 2 weeks
- I stared to generate some random character and display them in a tPaintBox
- the definition of the Composite tree came first
- the positioning of the random characters in rows came next
- the selection rectangle, with font change showed that the classes had the correct attributes and that the layout methods worked as expected
- then I added all the patterns, using the Gof order:
- Decorator, Strategy, Iterator, Visitor
Iterator, which by the way is required by Strategy, was the most difficult of those
- at the end I tried the Bridge. This forced a complete rewrite, since the display part had to be reorganized. It took about half of the development time. Not because Bridge is that complex, but because we tried to make it
look natural. Pretending you are using X-Windows and Presentation Manager while you are sitting on top of Windows took some effort.
- the easy part was Command, and I knew it. The c_caret and c_selection classes were then written
So what is the conclusion ? Well, having now a resonable pattern experience and
some realistic piece of code, I will read the Design Pattern book a second time, but with a deeper understanding.
11.5 - The natural order What would be the most natural order of learning patterns ? In the Lexi case, I
would recommend: - bypass the Abstract Factory and Bridge, they are a pain in the neck, and how many people really develop for Windows, X Windows and Presentation
Manager all the time anyway ?
But decide at this early stage how you are going to draw your glyphs (pass a tPaintBox to the drawing routines, or only a tCanvas, put a tCanvas
reference in each c_glyph or pass it as a parameter etc) - Composite is the mandatory first step
- Decorator would be a nice test for the understanding of the Composite
- Iterator is the first roadblock. And a tough one on a tree like structure. Iterator will then be used by Visitor and Strategy
- from then on, it will be rock, rattle and roll.
11.6 - How to learn pattern Does it make sense to publish the Lexi code ? Many will argue that by definition, patterns are concepts, and showing code that used those concepts will kill the abstraction. Quite so.
I you want to get familiar with the Design Patterns, my advice would be to put your hands on any of the classical design example (Point of Sale, NextGen from Larman, the Video Sales Shop, Automatic Teller Machine, the Steam Boiler, the
Pet Shop from SUN and revised by Microsoft, etc), and do write the code implementing the example, paying special attention to the organization of classes. Using Lexi simplified the pattern part, since the Gof told me where
the pattern could be used. If you do not find any suitable example, why not use the Gof Lexi case study: it worked for me. But be prepared to buy a second Gof book: by the time you
are done, your current version will be completely worn out. That is the goal of the exercise, after all.
12 - Download the Source Code
We placed all the sources for the projects in the following .ZIP files:
The .ZIP files contain:
- the main program (.DPR, .DOF, .RES), the main form (.PAS, .DFM), and any other auxiliary form
- any .TXT for parameters
- all units (.PAS) for units
The .ZIP - is self-contained: you will not need any other product.
- can be used from any folder (the pathes are RELATIVE)
- will not modify your PC in any way beyond the path where you placed the .ZIP (no registry changes, no path creation etc).
To use the .ZIP:
- create or select any folder of your choice
- unzip the downloaded file
- using Delphi, compile and execute
To remove the .ZIP simply delete the folder.
As usual:
- please tell us at fcolibri@felix-colibri.com if you found some errors, mistakes, bugs, broken links or had some problem downloading the file. Resulting corrections will
be helpful for other readers
- we welcome any comment, criticism, enhancement, other sources or reference suggestion. Just send an e-mail to fcolibri@felix-colibri.com.
- or more simply, enter your (anonymous or with your e-mail if you want an answer) comments below and clic the "send" button
- and if you liked this article, talk about this site to your fellow developpers, add a link to your links page ou mention our articles in your blog or newsgroup posts when relevant. That's the way we operate:
the more traffic and Google references we get, the more articles we will write.
13 - Conclusion Implementing Lexi was one of the easiest way to assimilate the pattern
techniques. I certainly would encourage those starting to learn patterns to code a full fledged project in a similar fashion. By the way, the figures presented here have been drawn using Lexi's grand daddy
(Fig, a figure editor) which was first written in UCSD Pascal. And the text is written using Lexi's grand grand daddy (Ed, the text editor), which started way back in 1980 as a 6502 assembler program. So, keeping in touch with
the evolution of programming concepts was vital to maintain both projects. And maintenance, as testified by the longevity of both tools, is really the name of the game.
14 - References
- Erich GAMMA, Richard HELM, Ralph JOHNSON, John VLISSIDES
Design Patterns - Elements of Reusable Object-Oriented Software Addison Wesley - 1994 - ISBN 0-201-63361-2
- Paul CALDER, Mark LINTON
The Object Oriented Implementation of a Document Editor OOPSLA 1992 - Proceedings pages 154-165 Vancouver - ACM Press
- Niklaus Wirth
Algorithms+ Data Structure= Programming Prentice Hall - 1976 - ISBN 0-13-022418-9 - Bertrand MEYER
Object Oriented Software Construction Prentice Hall - 1997 (2nd Edition) - ISBN 0-13-629155-4 - Bertrand MEYER
UML, the Positive Spin
Published in Software Development, Vol. 5 No. 9, September 1997, as "The View from the Eiffel Tower"
A paper criticising UML - Martin BUCHI, Wolfgang WECK
Generic Wrapping
Turku Center for Computer Science, April 2000 Report 317
15 - Other Papers with Source and Links
Database |
database reverse engineering | Extraction of the Database Schema by analyzing the content of the application's .DFMs
| sql parser | Parsing SQL requests in Delphi, starting from an EBNF grammar for SELECT, INSERT and UPDATE |
ado net tutorial |
a complete Ado Net architectural presentation, and projects for creating the Database, creating Tables, adding, deleting and updating rows, displaying the data in controls and DataGrids, using in memory DataSets, handling Views, updating the Tables with a DataGrid
| turbo delphi interbase tutorial |
develop database applications with Turbo Delphi and Interbase. Complete ADO Net architecture, and full projects to create the database, the Tables, fill the rows, display and update the values with DataGrids. Uses the BDP |
bdp ado net blobs | BDP and Blobs : reading and writing Blob fields using the BDP with Turbo Delphi |
interbase stored procedure grammar |
Interbase Stored Procedure Grammar : The BNF Grammar of the Interbase Stored Procedure. This grammar can be used to build stored procedure utilities, like pretty printers, renaming tools, Sql Engine conversion or ports |
using interbase system tables |
Using InterBase System Tables : The Interbase / FireBird System Tables: description of the main Tables, with their relationship and presents examples of how to extract information from the schema |
eco tutorial |
Writing a simple ECO application: the UML model, the in memory objects and the GUI presentation. We also will show how to evaluate OCL expressions using the EcoHandles, and persist the data on disc |
delphi dbx4 programming |
the new dbExpress 4 framework for RAD Studio 2007 : the configuration files, how to connect, read and write data, using tracing and pooling delegates and metadata handling |
blackfishsql |
using the new BlackfishSql standalone database engine of RAD Studio 2007 (Win32 and .Net) : create the database, create / fill / read Tables, use Pascal User Defined Functions and Stored Procedures |
rave pdf intraweb |
how to produce PDF reports using Rave, and have an Intraweb site generate and display .PDF pages, with multi-user access |
embarcadero er studio |
Embarcadero ER Studio tutorial: how to use the Entity Relationship tool to create a new model, reverse engineer a database, create sub-models, generate reports, import metadata, switch to Dimensional Model | | |
Web |
sql to html | converting SQL ascii request to HTML format
| simple web server |
a simple HTTP web Server and the corresponding HTTP web Browser, using our Client Server Socket library |
simple cgi web server |
a simple CGI Web Server which handles HTML <FORM> requests, mainly for debugging CGI Server extension purposes |
cgi database browser | a CGI extension in order to display and modify a Table using a Web Browser |
whois | a Whois Client who requests information about owners of IP adresses. Works in batch mode. |
web downloader |
an HTTP tool enabling to save on a local folder an HTML page with its associated images (.GIF, .JPEG, .PNG or other) for archieving or later off-line reading |
web spider | a Web Spider allowing to download all pages from a site, with custom or GUI filtering and selection. |
asp net log file |
a logging CLASS allowing to monitor the Asp.Net events, mainly used for undesrtanding, debugging and journaling Asp.Net Web applications |
asp net viewstate viewer |
an ASP.NET utility displaying the content of the viewtate field which carries the request state between Internet Explorer and the IIS / CASSINI Servers |
rss reader |
the RSS Reader lets you download and view the content of an .RSS feed (the entry point into somebody's blog) in a tMemo or a tTreeView. Comes complete with an .HTML downloader and an .XML parser |
news message tree |
how to build a tree of the NNTP News Messages. The downloaded messages are displayed in tListBox by message thread (topic), and for each thread the messages are presented in a tTreeVi"ew |
threaded indy news reader |
a NewsReader which presents the articles sorted by thread and in a logical hierarchical way. This is the basic Indy newsreader demo plus the tree organization of messages |
delphi asp net portal programming |
presentation, architecture and programming of the Delphi Asp Net Portal. This is a Delphi version of the Microsoft ASP.NET Starter Kit Web Portal showcase. With detailed schemas and step by step presentation, the Sql scripts and binaries of the Database
| delphi web designer |
a tiny Delphi "RAD Web Designer", which explains how the Delphi IDE can be used to generate .HTML pages using the Palette / Object Inspector / Form metaphor to layout the page content |
intraweb architecture |
the architecture of the Intraweb web site building tool. Explains how Delphi "rad html generator" work, and presents the CLASS organization (UML Class diagrams) |
ajax tutorial |
AJAX Tutorial : writing an AJAX web application. How AJAX works, using a JavaScript DOM parser, the Indy Web Server, requesting .XML data packets - Integrated development project |
asp net master pages |
Asp.Net 2.0 Master Pages : the new Asp.Net 2.0 allow us to define the page structure in a hierarchical way using Master Pages and Content Pages, in a way similar to tForm inheritance |
delphi asp net 20 databases |
Asp.Net 2.0 and Ado.Net 2.0 : displaying and writing InterBase and Blackfish Sql data using Dbx4, Ado.Net Db and AdoDbxClient. Handling of ListBox and GridView with DataSource components
| asp net 20 users roles profiles |
Asp.Net 2.0 Security: Users, Roles and Profiles : Asp.Net 2.0 offers a vaslty improved support for handling security: new Login Controls, and services for managing Users, grouping Users in Roles, and storing User preferences in Profiles
| bayesian spam filter |
Bayesian Spam Filter : presentation and implementation of a spam elimination tool which uses Bayesian Filtering techniques | | |
TCP/IP |
tcp ip sniffer | project to capture and display the packets travelling on the Ethernet network of your PC. |
sniffing interbase traffic |
capture and analysis of Interbase packets. Creation of a database and test table, and comparison of the BDE vs Interbase Express Delphi components |
socket programming | the simplest Client Server example of TCP / IP communication using Windows Sockets with Delphi |
delphi socket architecture |
the organization of the ScktComp unit, with UML diagrams and a simple Client Server file transfer example using tClientSocket and tServerSocket | | |
Object Oriented Programming Components |
delphi virtual constructor |
VIRTUAL CONSTRUCTORS together with CLASS references and dynamic Packages allow the separation between a main project and modules compiled and linked in later. The starting point for Application Frameworks and Plugins
| delphi generics tutorial |
Delphi Generics Tutorial : using Generics (parameterized types) in Delphi : the type parameter and the type argument, application of generics, constraints on INTERFACEs or CONSTRUCTORs | |
| UML Patterns |
the lexi editor |
delphi source code of the Gof Editor: Composite, Decorator, Iterator, Strategy, Visitor, Command, with UML diagrams |
factory and bridge patterns |
presentation and Delphi sources for the Abstract Factory and Bridge patterns, used in the Lexi Document Editor case study from the GOF book |
gof design patterns |
delphi source code of the 23 Gof (GAMMA and other) patterns: Composite, Decorator, Iterator, Strategy, Visitor, Command | | |
| Graphic |
delphi 3d designer |
build a 3d volume list, display it in perspective and move the camera, the screen or the volumes with the mouse. |
writing a flash player |
build your own ShockWave Flash movie Player, with pause, custom back and forward steps, snapshots, resizing. Designed for analyzing .SWF demos. | | |
Utilities |
the coliget search engine |
a Full Text Search unit allowing to find the files in a directory satisfying a complex string request (UML AND Delphi OR Patters) |
treeview html help viewer |
Treeview .HTML Help Viewer : the use of a Treeview along with a WebBrowser to display .HTML files alows both structuring and ordering of the help topics. This tool was used to browse the Delphi PRISM Wiki help. | |
| Delphi utilities |
delphi net bdsproj |
structure and analysis of the .BDSPROJ file with the help of a small Delphi .XML parser | dccil bat generator
| generation of the .BAT for the Delphi DCCIL command line compiler using the .BDSPROJ | dfm parser |
a Delphi Project analyzing the .DFM file and building a memory representation. This can be used for transformations of the form components |
dfm binary to text | a Delphi Project converting all .DFM file from a path from binary to ascii format |
component to code |
generate the component creation and initialization code by analyzing the .DFM. Handy to avoid installing components on the Palette when examining new libraries |
exe dll pe explorer |
presents and analyzes the content of .EXE and .DLL files. The starting point for extracting resources, spying .DLL function calls or injecting additional functionalities |
dll and process viewer |
analyze and display the list of running processes, with their associated DLLs and Memory mapped files (Process Walker) | | |
Controls |
find memo | a tMemo with "find first", "find next", "sort", "save" capabilities | | |
|
16 - The Author
Felix John COLIBRI works at the Pascal Institute. Starting with Pascal in 1979, he then became involved with Object Oriented Programming, Delphi, Sql, Tcp/Ip, Html, UML. Currently, he is mainly
active in the area of custom software development (new projects, maintenance, audits, BDE migration, Delphi
Xe_n migrations, refactoring), Delphi Consulting and Delph
training. His web site features tutorials, technical papers about programming with full downloadable source code, and the description and calendar of forthcoming Delphi, FireBird, Tcp/IP, Web Services, OOP / UML, Design Patterns, Unit Testing training sessions. |