|
Abstract Factory and Bridge Design Patterns - Felix John COLIBRI.
|
- abstract : presentation of Abstract Factory and Bridge. Example in the
Lexi Document Editor
- key words : Design Patterns, Abstract Factory, Bridge, Gof, Gang Of Four,
Lexi
- software used : Windows XP, Delphi 6
- hardware used : Pentium 1.400Mhz, 256 M memory, 140 G hard disc
- scope : Delphi 1 to 8 for Windows, Kylix
- level : Delphi developer
- plan :
1 - Introduction
When we wrote the Lexi code, we followed the Gof order: Composite, Strategy,
Decorator, Abstract Factory, Bridge, Command, Iterator and Visitor.
However Strategy and Decorator use Iterator, and Factory and Bridge are
required as soon as you start drawing, which is right at the beginning, since
c_glyph, the base of the whole demonstration, will contain some drawing
methods.
It turns out that Factory and Bridge are complex patterns because:
- they involve many participants
- they assume that you will be using several windowing libraries (Win32,
X-Windows, Presentation Manager). You might develop cross platform code, but
I very much doubt that any Design Pattern student will work with a
multi-windowing development tool to learn patterns.
It is possible to follow the Gof example, imagining that you are on such
multi-windowing tool. But you cannot test, change, or adapt the example. The
goal of our Lexi coding effort was to allow this hands-on experience.
Therefore we simulated the "multi-windowing" development library, and included
it in our Lexi project.
2 - The Round and Square Control Worlds
2.1 - Look and Feel
We offer two different window controls libraries:
- the "round" control world, where all shapes (button, label, selection
rectangle) have rounded edges
- the "square" control world, where rectangle have their usual square edges
Here are two snapshots showing the difference:
2.2 - The Control specification
In order to simulate different libraries, we used different display (round vs
square) but also the definitions of our controls.
To specify and rectangular buttons, we have many choices:
- use Left, Top, Width and Height (Delphi)
- use two opposite corners (X1, Y1, X2, Y2) like Win23 Rectangle
- any other 4 parameters could be used (X, Y, angle to the center and
diagonal size etc)
The drawing can be performed
- whith a library specific Rectangle function (Win32)
- using a PostScript-like library (SetPath, LineTo, StrokePath etc)
- with a page description (PDF)
- at a higher level with tagged languages like HTML, XML, possibly style
sheets
- or at the opposite corner, directly changing the colors of the screen
pixels, what everybody ends up doing anyway
And to handle user actions, we can use:
- interrupt redirection (DOS): the action on the keybord calls an interrupt
handler
- event driven call backs (Mac, Windows): the OS send a message and the
application's message loop dispatches the message to an event handler
- TPC-IP communications (X Windows): whenever the user hits "A" on the
keyboard, a TCP-IP packet is sent off to Hawaii, travels all the way to
Japan, checks what's happening Down Under, and finally comes running back in
New York to print 'A' on your screen. Fine.
We chose to use
- 2 kind of rectangle specification
- X1, Y1, X2, Y2 for the square controls
- X1, Y1, horizontal size and percentage height for the round controls
- a single event call back mechanism for user actions
Our choices were not made for efficiency or code simplicity: we would have used
the Delphi convention. But then there would have been neither Abstract
Factory nor Bridge to talk about ... Certainly we could have obfuscated the
matter a little bit more and made the trip all the way to Hawaii, but I think
enough is enough.
2.3 - The Square Class
Here is the definition of the square control ancestor:
c_square_abstract_control= Class(c_abstract_control)
m_x_1, m_y_1, m_x_2, m_y_2: Integer;
constructor create_square_abstract_control(p_name: String;
p_c_control_list_ref: c_control_list;
p_x_1, p_y_1, p_x_2, p_y_2: Integer);
function f_display_control: String; Override;
function f_hit_test(p_x, p_y: Integer): Boolean; Override;
// -- the library primitives for drawing on the control
procedure draw_blank_rectangle;
procedure draw_square_shape;
end; // c_square_abstract_control
|
From this ancestor, we derive the real controls: buttons, labels, scroll bars
etc. The button definition, for instance is the following:
c_square_button_control= Class(c_square_abstract_control)
m_caption: String;
constructor create_square_button_control(p_name: String;
p_c_control_list_ref: c_control_list;
p_x_1, p_y_1, p_x_2, p_y_2: Integer;
p_on_click: t_on_click);
procedure handle_control_mouse_down(p_x, p_y: Integer); Override;
procedure handle_control_mouse_up(p_x, p_y: Integer); Override;
procedure draw_control; Override;
end; // c_square_button_control
|
We simply added the additional attributes (caption in our case) and event
handler (mouse up and down to display the shadow change which visually shows
the push down effect)
2.4 - The Round class
In a similar fashion, we have devifed a "round" hierarchy. Here is the abstract
round control:
c_round_abstract_control= Class(c_abstract_control)
m_x_1, m_y_1, m_size: Integer;
m_percent: Double;
constructor create_round_abstract_control(p_name: String;
p_c_control_list_ref: c_control_list;
p_x_1, p_y_1, p_size: Integer; p_percent: Double);
function f_hit_test(p_x, p_y: Integer): Boolean; Override;
procedure draw_blank_rectangle;
procedure draw_round_rectangle(p_x_1, p_y_1, p_size, p_percent: Integer);
end; // c_round_abstract_control
|
2.5 - The abstract control
You might have noticed that both square and round controls have a
c_abstract_control ancestor. Are we cheating by any chance ?
Well yes and no. In this case:
- we must keep an access to our controls. This could be through global
variables, but, quite naturally, it is better to use some kind of list. In
the same way that Delphi uses Components and Controls attached to each
tForm
- whenever we have to encapsulate a tList or a tStringlist, we start from a
template unit with a basic class and a tList or tStringlist container
class, and simply change the names
- in our case, the unit for the round or square controls would have been
identical, but for the "round" or "square" name. So we decided to use the
same unit, and made the basic class the common ancestor of the
c_round_control and c_square_control
Here is this list encapsulation:
t_on_mouse_down= Procedure(p_x, p_y: Integer) of Object;
t_on_click= Procedure of Object;
c_control_list= Class; // forward
c_abstract_control= Class(c_basic_object)
m_c_control_list_ref: c_control_list;
m_on_click: t_on_click;
m_on_mouse_down, m_on_mouse_move, m_on_mouse_up: t_on_mouse_down;
Constructor create_abstract_control(p_name: String;
p_c_control_list_ref: c_control_list);
function f_display_control: String; Virtual;
function f_hit_test(p_x, p_y: Integer): Boolean; Virtual;
procedure draw_control; Virtual; Abstract;
procedure handle_control_mouse_down(p_x, p_y: Integer); Virtual;
procedure handle_control_mouse_move(p_x, p_y: Integer); Virtual;
procedure handle_control_mouse_up(p_x, p_y: Integer); Virtual;
end; // c_abstract_control
c_control_list= class(c_basic_object)
m_c_paintbox_ref: tPaintbox;
m_c_canvas_ref: tCanvas;
m_c_save_canvas_stack: c_save_canvas_stack;
m_c_control_list: tStringList;
Constructor create_control_list(p_name: String;
p_c_paintbox_ref: tPaintBox);
function f_control_count: Integer;
function f_c_abstract_control(p_control_index: Integer): c_abstract_control;
function f_index_of(p_control_name: String): Integer;
procedure add_control(p_control_name: String; p_c_abstract_control: c_abstract_control);
procedure display_control_list;
function f_c_find_mouse_control(p_x, p_y: Integer): c_abstract_control;
procedure handle_mouse_down(p_x, p_y: Integer);
procedure handle_mouse_move(p_x, p_y: Integer);
procedure handle_mouse_up(p_x, p_y: Integer);
procedure draw_control_list;
Destructor Destroy; Override;
end; // c_control_list
|
You may check that this list does not contain any "round" or "square" specific
code.
2.6 - Using the Controls
If we want to use the control, say the "square" controls, the client code would
use constructors like this:
var g_c_control_list: c_control_list= Nil;
procedure TForm1.on_insert_button_click;
begin
display('insert');
Panel1.Color:= ClRed;
end; // on_insert_button_click
procedure TForm1.create_square_world_Click(Sender: TObject);
var l_c_square_button: c_square_button_control;
begin
g_c_control_list:= c_control_list.create_control_list('control_list', PaintBox1);
l_c_square_button:= c_square_button_control.create_square_button_control('Insert',
g_c_control_list, 10, 10, 120, 35, on_insert_button_click);
end; // create_square_world_Click
|
Should we want to use the other library, we would have called:
procedure TForm1.create_round_world_Click(Sender: TObject);
var l_c_round_button: c_round_button_control;
begin
g_c_control_list:= c_control_list.create_control_list('surface', PaintBox1);
l_c_round_button:= c_round_button_control.create_round_button_control('Insert',
g_c_control_list, 10, 40, 110, 0.2272, on_insert_button_click);
end; // create_round_world_Click
|
The programmer therefore will have to remember all the subtelties of the
different calling conventions, like the radius offset and conversion ratios
used to have the same final result
Enters the Abstract Factory pattern.
3 - The Abstract Factory Pattern
3.1 - Objectives
The purpose of this pattern is:
- to offer a common single convention creating controls from different
windowing libraries
- to hide the details of the creation in a separate class
Assuming that the only controls we want to use are buttons, labels, drawing
area and scrollbars, we will define the following classes:
c_abstract_button_widget= Class(c_basic_object)
end; // c_abstract_button_widget
c_abstract_label_widget= Class(c_basic_object)
end; // c_abstract_label_widget
c_abstract_draw_surface_widget= Class(c_basic_object)
end; // c_abstract_draw_surface_widget
c_abstract_scrollbar_widget= Class(c_basic_object)
end; // c_abstract_draw_surface_widget
c_abstract_widget_factory=
class(c_basic_object)
m_c_control_list_ref: c_control_list;
Constructor create_abstract_widget_factory(p_name: String;
p_c_control_list_ref: c_control_list);
function f_c_create_button_widget(p_caption: String;
p_left, p_top, p_width, p_height: Integer;
p_on_click: t_on_click): c_abstract_button_widget; Virtual; Abstract;
function f_c_create_label_widget(p_caption: String;
p_left, p_top, p_width, p_height: Integer): c_abstract_label_widget; Virtual; Abstract;
function f_c_create_draw_surface_widget(p_caption: String;
p_left, p_top, p_width, p_height: Integer;
p_on_mouse_down, p_on_mouse_move, p_on_mouse_up: t_on_mouse_down):
c_abstract_draw_surface_widget; Virtual; Abstract;
function f_c_create_scrollbar_widget(p_caption: String;
p_left, p_top, p_width, p_height: Integer): c_abstract_scrollbar_widget; Virtual; Abstract;
procedure create_lexi_world(p_left, p_top, p_width, p_height: Integer);
end; // c_abstract_widget_factory
|
A you see, we have standardized all the creation conventions using the Delphi
Left, Top, Width and Height parameters. In addition those classes are all
ABSTRACT (they are either empty or contain VIRTUAL; ABSTRACT methods).
Now if our user wants to use square controls, he will use descendents of both
the factory and of the abstract widgets:
// -- the width height square widgets
c_square_button_widget= Class(c_abstract_button_widget)
m_c_square_button_control: c_square_button_control;
Constructor create_square_button_widget(p_name: String;
p_c_abstract_widget_factory: c_abstract_widget_factory;
p_left, p_top, p_width, p_height: Integer;
p_on_click: t_on_click);
end; // c_square_button_widget
// -- ... here the other square widgets
// -- the concrete square widget factory
c_square_widget_factory=
class(c_abstract_widget_factory)
Constructor create_square_widget_factory(p_name: String;
p_c_control_list_ref: c_control_list);
function f_c_create_button_widget(p_caption: String;
p_left, p_top, p_width, p_height: Integer;
p_on_click: t_on_click): c_abstract_button_widget; Override;
function f_c_create_label_widget(p_caption: String;
p_left, p_top, p_width, p_height: Integer): c_abstract_label_widget; Override;
function f_c_create_draw_surface_widget(p_caption: String;
p_left, p_top, p_width, p_height: Integer;
p_on_mouse_down, p_on_mouse_move, p_on_mouse_up: t_on_mouse_down):
c_abstract_draw_surface_widget; Override;
function f_c_create_scrollbar_widget(p_caption: String;
p_left, p_top, p_width, p_height: Integer): c_abstract_scrollbar_widget; Override;
end; // c_abstract_factory
|
and the user will call:
procedure TForm1.create__Click(Sender: TObject);
var l_c_widget_factory: c_abstract_widget_factory;
l_running_y, l_running_x: Integer;
procedure add_button(p_caption: String; p_width: Integer; p_click_event: t_on_click);
begin
with l_c_widget_factory do
f_c_create_button_widget(p_caption, l_running_x, l_running_y, p_width, k_button_default_height,
p_click_event);
Inc(l_running_x, p_width+ 5);
end; // add_button
begin // create_Click
g_c_control_list:= c_control_list.create_control_list('surface', PaintBox1);
l_c_widget_factory:= c_square_widget_factory.create_square_widget_factory('square_fact', g_c_control_list);
l_running_y:= 5;
l_running_x:= 5;
add_button('<justify_button', 43, Nil);
add_button('|justify|_button', 43, Nil);
add_button('justify >_button', 47, Nil);
add_button('justify Z_button', 47, Nil);
l_c_widget_factory.Free;
l_c_widget_factory:= c_round_widget_factory.create_round_widget_factory('round_fact', g_c_control_list);
l_running_y:= 35;
l_running_x:= 5;
add_button('<justify_button', 43, Nil);
add_button('|justify|_button', 43, Nil);
add_button('justify >_button', 47, Nil);
add_button('justify Z_button', 47, Nil);
l_c_widget_factory.Free;
end; // create_Click
|
with the following result:
Also notice that the Abstract Factory not only unifies the calling
conventions but also allows RUN TIME change of the creation (in the above
example, be creating different concrete factories, we could use controls of
both kinds in the same application)
3.2 - UML Class Diagrams
The UML diagram of the Abstract Factory is the following:
In the previous figure, we have highlighted in blue what has to be added when
we want to use a new widget:
- the abstract widget and its two concrete implementations
- methods for the creation in the abstract an in the concrete factories
When we add a new windowing system, we have to
- create new concrete widgets
- create a new concrete factory
This is illustrated by coloring the addition in green:
3.3 - The Class Explosion
As seen previously, the addition of a new control will lead to the addition of
3 new classes (in addition to the 3 functions). This is sometimes criticized as
a "class explosion". But this is the price to pay for the ability to use a
single call in the client code for each control creation
3.4 - Selection of the Concrete Factory
In our example, all creations took place in a single OnClick event. So we
declared the Factory as a local variable, used this variable to perform the
creation, and freed the factory at the end.
var l_c_widget_factory: c_abstract_widget_factory;
l_c_button_widget: c_abstract_button_widget;
begin
l_c_widget_factory:= c_square_widget_factory.create_square_widget_factory('square_fact', ...
l_c_button_widget:= l_c_widget_factory.f_c_create_button_widget('insert', ...
...
l_c_widget_factory.Free;
end;
|
If we do not reference the Factory variable elsewhere, we could even use a
"super local" variable within the WITH clause:
var l_c_button_widget: c_abstract_button_widget;
begin
WITH c_square_widget_factory.create_square_widget_factory('square_fact', ... ) DO
BEGIN
l_c_button_widget:= f_c_create_button_widget('insert', ...
...
Free;
END; // WITH c_square_widget_factory
end
|
So, for all the mutliple windowing systems, we need only a single Factory
CLASS. In addition, in the client code, we only need a single Factory
object. So the Gof suggest to treat the Factory class as a Singleton class.
This factory would then be fetched using a call like
var l_c_button_widget: c_abstract_button_widget;
begin
l_c_button:= c_abstract_widget_factory.f_c_instance.f_c_create_button_widget('insert' ...
end;
|
This is quite elegant. But how is the kind of concrete factory chosen ? The Gof
simply show this kind of call.
So we investigated a little bit more. The fact that we live in a square or
round world HAD to be used to select the concrete factory, but where was this
done ?
Well it turns out that in the Singleton chapter, they suggest using an
ENVIRONMENT VARIABLE. Of all things ! We used a local variable, even a "super
local", and they suggest a sytem wide global !
Naturally there are other ways to select the concrete singleton, but I wanted
to highlighte this recommendation of an environment variable.
You may use without any difficulties a Singleton for this example, but we will
stick to our local variables, explicitly creating the required concrete
factory.
4 - Bridge
4.1 - Standardizing Behaviour
Abstract Factory allows us to create widgets from different window control
libraries with uniform creation calls. But this is a static structural problem.
What about interacting with those widgets after their creation ?
The widgets created by the Factory could be modified to include in each
control kind the attributes and methods specific to this control:
However, each time we want to add a new control, we face the class explosion
problem and have many changes to do.
Bridge solves this by allowing to change the widget hierarchy independently
from the windowing hierarchy. The hierarchy is separated in two parts:
- on one hand, the routines to perform the primitive functions are modeled in
a c_gadget_implementation hierarchy, which will enable to draw text, lines,
rectangle.
This hierarchy hides all windowing library by defining an interface for
performing all actions on our controls in a uniform (library independent)
way
- on the other hand the different controls are modeled by c_gadget hierarchy,
whose children contain c_button_gadget, c_label_gadget etc.
This hierarchy is independent of the windowing library chosen:
- the ancestor may define some methods used by all the descendent classes
(draw text, draw rectangle)
- the descendent have methods specific to their task (set a check, fetch an
ItemIndex)
In both cases, the work is performed using only the c_gadget_implementation
interface, not the library specific routines.
Here is the UML representation:
In this diagram:
- c_gadget_implementation
- exports raw tCanvas routines, like set_pen_color
- might add some methods using the previous one (draw_triangle which uses
draw_line)
- c_abstract_gadget uses m_c_gadget_implementation_ref to add other methods
used by all its concrete descendents (draw_rectangle)
- the concrete descendents use m_c_gadget_implementation_ref to add specific
methods (fill_rectangle)
- the client only uses the c_gadget concrete descendents, never directly
using c_gadget_implementation or its reference
m_c_gadget_implementation_ref
4.2 - Bridge summary
Bridge is a complex pattern though.
In their book, the Gof take the window / window_implementation example. They
tell us about "IconWindows" and "TransientWindows", "floating palettes". I
assume this means "minimized" window, "dialog" and "dialog with a toolbar". The
implementation part then offers all kinds of drawing primitives. Does that mean
that to minimize an X-Windows window, we have to draw them ourselves on the
screen using the implementation routines ? This would be awfull: we would
rebuild the library down to the last pixel. If this is not the recommendations,
there should be some library routines, like x_window.iconify() and
pm_window.iconify(), but they are not present in the window implementation
classes.
Their sample code only talks about drawing inside a window: so bridge would be
some kind of "generalized canvas". This bridge example only handles the content
drawing of windows. The draw_content is then adapted to the specific control,
displaying text for an Edit, or loading a bitmap for an minimized window. But
this has nothing to do with control specific generalization (scrollbar
position, ItemIndex etc). In addition, the minimized display or scroll
position are handled transparently by most windowing libraries: we might have
to encapsulate different method names and parameters, but will never resort to
actually drawing the scrollbar thumb or marking a checkbox.
In our example, we took the more general position of a Bridge used for ALL
control handling: the control-specific tasks (scrollbar position) as well as
the general tasks (drawing the content).
4.3 - Our implementation
Here is the gadget_implementation, which encapsulates the windowing libraries
specificities part:
t_on_gadget_mouse_down= u_c_control.t_on_mouse_down;
c_abstract_gadget_implementation=
Class(c_basic_object)
m_c_control_list_ref: c_control_list;
constructor create_abstract_gadget_implementation(p_name: String;
p_c_control_list_ref: c_control_list);
// -- label
procedure set_label_caption(p_caption: String); Virtual; Abstract;
// -- scrollbar
procedure set_scrollbar_position(p_scrollbar_name: String;
p_position: Integer); Virtual; Abstract;
function f_get_scrollbar_position: Integer; Virtual; Abstract;
// -- drawing
procedure save_device_context; Virtual; Abstract;
procedure restore_device_context; Virtual; Abstract;
procedure set_pen_color(p_pen_color: Integer); Virtual; Abstract;
procedure set_brush_style(p_brush_style: tBrushStyle); Virtual; Abstract;
// -- here placed the code at this level rather than duplicating
// -- it for the demo
procedure set_pen_mode(p_pen_mode: tPenMode); Virtual;
procedure set_brush_color(p_brush_color: Integer); Virtual;
procedure canvas_set_font_height(p_font_height: Integer); Virtual;
function f_canvas_get_font_height: Integer; Virtual;
procedure canvas_draw_bitmap(p_x, p_y: Integer; p_c_bitmap: tBitMap); Virtual;
function f_canvas_character_width(p_character: Char): Integer; Virtual; Abstract;
function f_canvas_character_height(p_character: Char): Integer; Virtual; Abstract;
procedure canvas_line(p_x_1, p_y_1, p_x_2, p_y_2: Integer); Virtual; Abstract;
procedure draw_rectangle(p_left, p_top, p_width, p_height: Integer); Virtual; Abstract;
procedure device_text_out(p_x, p_y: Integer; p_text: String); Virtual; Abstract;
procedure fill_rectangle(p_left, p_top, p_width, p_height,
p_color: Integer); Virtual; Abstract;
end; // c_abstract_gadget_implementation
|
The c_square_gadget_implementation and c_round_gadget_implementation
definitions are similar to this abstract ancestor.
Here is the c_abstract_gadget, which will use the
c_abstract_gadget_implementation class:
c_abstract_gadget=
class(c_basic_object)
m_c_abstract_gadget_implementation_ref: c_abstract_gadget_implementation;
m_gadget_left, m_gadget_top, m_gadget_width, m_gadget_height: Integer;
m_on_mouse_down, m_on_mouse_move, m_on_mouse_up: t_on_gadget_mouse_down;
Constructor create_abstract_gadget(p_name: String;
p_c_abstract_gadget_implementation_ref: c_abstract_gadget_implementation;
p_gadget_left, p_gadget_top, p_gadget_width, p_gadget_height: Integer);
function f_display_gadget: String; Virtual;
procedure save_graphic_context;
procedure restore_graphic_context;
procedure set_pen_color(p_color: Integer);
procedure set_pen_mode(p_pen_mode: tPenMode);
procedure set_brush_style(p_brush_style: tBrushStyle);
procedure set_brush_color(p_brush_color: Integer);
procedure set_font_height(p_font_height: Integer);
function f_get_font_height: Integer;
function f_character_width(p_character: Char): Integer;
function f_character_height(p_character: Char): Integer;
procedure draw_line(p_x_1, p_y_1, p_x_2, p_y_2: Integer);
procedure draw_triangle(p_left, p_top, p_width, p_height: Integer);
procedure draw_rectangle(p_left, p_top, p_width, p_height: Integer);
procedure draw_text(p_x, p_y: Integer; p_text: String); Virtual;
procedure shrink_gadget(p_delta_left, p_delta_top, p_delta_right, p_delta_bottom: Integer); Virtual;
procedure fill_rectangle(p_left, p_top, p_width, p_height, p_color: Integer);
procedure draw_bitmap(p_x, p_y: Integer; p_c_bitmap: tBitmap);
procedure draw_content; Virtual; Abstract;
end; // c_abstract_gadget
|
The c_abstract_gadget offers some routines which might be used by descendent
concrete gadgets. If all gadgets had to draw triangles, we could use:
c_button_gadget= class(c_abstract_gadget)
Constructor create_button_gadget(p_name: String;
p_c_abstract_gadget_implementation_ref: c_abstract_gadget_implementation;
p_gadget_left, p_gadget_top, p_gadget_width, p_gadget_height: Integer);
end; // c_dialog_gadget
c_label_gadget=
class(c_abstract_gadget)
Constructor create_label_gadget(p_name: String;
p_c_abstract_gadget_implementation_ref: c_abstract_gadget_implementation;
p_gadget_left, p_gadget_top, p_gadget_width, p_gadget_height: Integer);
procedure set_caption(p_caption: String);
end; // c_dialog_gadget
c_scrollbar_gadget=
class(c_abstract_gadget)
Constructor create_scrollbar_gadget(p_name: String;
p_c_abstract_gadget_implementation_ref: c_abstract_gadget_implementation;
p_gadget_left, p_gadget_top, p_gadget_width, p_gadget_height: Integer);
procedure draw_content; Override;
procedure shrink_gadget(p_delta_left, p_delta_top,
p_delta_right, p_delta_bottom: Integer); Override;
procedure set_position(p_position: Integer);
function f_get_position: Integer;
end; // c_scrollbar_gadget
c_draw_surface_gadget=
Class(c_abstract_gadget)
Constructor create_draw_surface_gadget(p_name: String;
p_c_abstract_gadget_implementation_ref: c_abstract_gadget_implementation;
p_gadget_left, p_gadget_top, p_gadget_width, p_gadget_height: Integer);
procedure draw_content; Override;
end; // c_draw_surface_gadge
|
And the concrete gadgets with some specific methods:
c_button_gadget= class(c_abstract_gadget)
Constructor create_button_gadget(p_name: String;
p_c_abstract_gadget_implementation_ref: c_abstract_gadget_implementation;
p_gadget_left, p_gadget_top, p_gadget_width, p_gadget_height: Integer);
end; // c_dialog_gadget
c_label_gadget=
class(c_abstract_gadget)
Constructor create_label_gadget(p_name: String;
p_c_abstract_gadget_implementation_ref: c_abstract_gadget_implementation;
p_gadget_left, p_gadget_top, p_gadget_width, p_gadget_height: Integer);
procedure set_caption(p_caption: String);
end; // c_dialog_gadget
c_scrollbar_gadget=
class(c_abstract_gadget)
Constructor create_scrollbar_gadget(p_name: String;
p_c_abstract_gadget_implementation_ref: c_abstract_gadget_implementation;
p_gadget_left, p_gadget_top, p_gadget_width, p_gadget_height: Integer);
procedure draw_content; Override;
procedure shrink_gadget(p_delta_left, p_delta_top,
p_delta_right, p_delta_bottom: Integer); Override;
procedure set_position(p_position: Integer);
function f_get_position: Integer;
end; // c_scrollbar_gadget
c_draw_surface_gadget=
Class(c_abstract_gadget)
Constructor create_draw_surface_gadget(p_name: String;
p_c_abstract_gadget_implementation_ref: c_abstract_gadget_implementation;
p_gadget_left, p_gadget_top, p_gadget_width, p_gadget_height: Integer);
procedure draw_content; Override;
end; // c_draw_surface_gadge
|
As an example, here is the code for the c_label_gadget:
Constructor c_label_gadget.create_label_gadget(p_name: String;
p_c_abstract_gadget_implementation_ref: c_abstract_gadget_implementation;
p_gadget_left, p_gadget_top, p_gadget_width, p_gadget_height: Integer);
begin
Inherited create_abstract_gadget(p_name, p_c_abstract_gadget_implementation_ref,
p_gadget_left, p_gadget_top, p_gadget_width, p_gadget_height);
end; // create_label_gadget
procedure c_label_gadget.set_caption(p_caption: String);
begin
draw_text(m_gadget_left, m_gadget_top, p_caption);
end; // c_dialog_gadget
|
We then added a c_abstract_gadget_factory:
c_abstract_gadget_factory=
class(c_basic_object)
m_c_control_list_ref: c_control_list;
_m_c_abstract_gadget_implementation_ref: c_abstract_gadget_implementation;
Constructor create_abstract_gadget_factory(p_name: String;
p_c_control_list_ref: c_control_list);
function f_c_create_button_gadget(p_caption: String;
p_left, p_top, p_width, p_height: Integer;
p_on_click: t_on_click): c_button_gadget; Virtual; Abstract;
function f_c_create_label_gadget(p_caption: String;
p_left, p_top, p_width, p_height: Integer): c_label_gadget; Virtual; Abstract;
function f_c_create_draw_surface_gadget(p_caption: String;
p_left, p_top, p_width, p_height: Integer;
p_on_mouse_down, p_on_mouse_move, p_on_mouse_up: t_on_mouse_down):
c_draw_surface_gadget; Virtual; Abstract;
function f_c_create_scrollbar_gadget(p_caption: String;
p_left, p_top, p_width, p_height: Integer): c_scrollbar_gadget; Virtual; Abstract;
function f_c_create_gadget_implementation(p_name: String):
c_abstract_gadget_implementation; Virtual; Abstract;
procedure create_lexi(p_left, p_top, p_width, p_height: Integer);
end; // c_abstract_gadget_factory
|
And here is the definition of the concrete square factories:
c_square_button_gadget= Class(c_button_gadget)
m_c_square_button_control: c_square_button_control;
Constructor create_square_button_gadget(p_name: String;
p_c_abstract_gadget_factory: c_abstract_gadget_factory;
p_left, p_top, p_width, p_height: Integer;
p_on_click: t_on_click);
end; // c_square_button_gadget
c_square_label_gadget= Class(c_label_gadget)
m_c_square_label_control: c_square_label_control;
Constructor create_square_label_gadget(p_name: String;
p_c_abstract_gadget_factory: c_abstract_gadget_factory;
p_left, p_top, p_width, p_height: Integer);
end; // c_square_label_gadget
c_square_draw_surface_gadget= Class(c_draw_surface_gadget)
m_c_square_draw_surface_control: c_square_draw_surface_control;
Constructor create_square_draw_surface_gadget(p_name: String;
p_c_abstract_gadget_factory: c_abstract_gadget_factory;
p_left, p_top, p_width, p_height: Integer;
p_on_mouse_down, p_on_mouse_move,
p_on_mouse_up: t_on_mouse_down);
end; // c_square_draw_surface_gadget
c_square_scrollbar_gadget= Class(c_scrollbar_gadget)
m_c_square_scrollbar_control: c_square_scrollbar_control;
Constructor create_square_scrollbar_gadget(p_name: String;
p_c_abstract_gadget_factory: c_abstract_gadget_factory;
p_left, p_top, p_width, p_height: Integer);
end; // c_square_scrollbar_gadget
c_square_gadget_factory= class(c_abstract_gadget_factory)
Constructor create_square_gadget_factory(p_name: String;
p_c_control_list_ref: c_control_list);
function f_c_create_button_gadget(p_caption: String;
p_left, p_top, p_width, p_height: Integer;
p_on_click: t_on_click): c_button_gadget; Override;
function f_c_create_label_gadget(p_caption: String;
p_left, p_top, p_width, p_height: Integer): c_label_gadget; Override;
function f_c_create_draw_surface_gadget(p_caption: String;
p_left, p_top, p_width, p_height: Integer;
p_on_mouse_down, p_on_mouse_move,
p_on_mouse_up: t_on_mouse_down): c_draw_surface_gadget; Override;
function f_c_create_scrollbar_gadget(p_caption: String;
p_left, p_top, p_width, p_height: Integer):
c_scrollbar_gadget; Override;
function f_c_create_gadget_implementation(p_name: String): c_abstract_gadget_implementation; Override;
end; // c_abstract_factory
|
And here is an example of the use of those classes:
- the creation of a button, a label, a scrollbar and a drawing surface
var g_c_button_gadget: c_button_gadget;
g_c_label_gadget: c_label_gadget;
g_c_scrollbar_gadget: c_scrollbar_gadget;
g_c_draw_surface_gadget: c_draw_surface_gadget;
procedure TForm1.create_bridge_Click(Sender: TObject);
var l_c_gadget_factory: c_abstract_gadget_factory;
l_c_gadget_implementation: c_abstract_gadget_implementation;
l_memo_rectangle: t_wh_rectangle;
begin
// -- 1 - the "windowing libraries"
g_c_control_list.Free;
g_c_control_list:= c_control_list.create_control_list('surface', PaintBox1);
// -- 2 - the abstract factory
display_square_or_round;
if square_.Checked
then l_c_gadget_factory:= c_square_gadget_factory.create_square_gadget_factory('square_fact',
g_c_control_list)
else l_c_gadget_factory:= c_round_gadget_factory.create_round_gadget_factory('round_fact',
g_c_control_list);
// -- now all the calls look the same, whether round or square library
with l_c_gadget_factory do
begin
// -- 3 - the gadget implementation
l_c_gadget_implementation:= f_c_create_gadget_implementation('implem');
// -- 4 some sample gadgets
if button_.Checked
then g_c_button_gadget:= f_c_create_button_gadget('Insert',
5, 5, 150, k_button_default_height, Nil);
if label_.Checked
then g_c_label_gadget:= f_c_create_label_gadget('font size',
5, 5+ k_button_default_height+ 5, 135, k_button_default_height);
l_memo_rectangle:= f_wh_rectangle(5, 5+ 2* (k_button_default_height+ 5),
PaintBox1.Width- 10- 25,
PaintBox1.Height- 5- 2* (k_button_default_height+ 5)- 5);
with l_memo_rectangle do
begin
if scroll_.Checked
then g_c_scrollbar_gadget:= f_c_create_scrollbar_gadget('scroll',
m_left+ m_width+ 5, m_top, 20, m_height);
if surface_.Checked
then g_c_draw_surface_gadget:= f_c_create_draw_surface_gadget('memo',
m_left, m_top, m_width, m_height,
Nil, Nil, Nil);
end; // with l_memo_rectangle
Free;
end; // with l_c_widget_factory
end; // create_bridge_Click
|
And here is the result:
- and we interact with the gadgets:
procedure TForm1.use_Click(Sender: TObject);
var l_c_bitmap: tBitmap;
begin
if label_.Checked
then g_c_label_gadget.set_caption('new caption');
if scroll_.Checked
then g_c_scrollbar_gadget.set_position(20);
if surface_.Checked
then
with g_c_draw_surface_gadget do
begin
draw_text(10, 110, 'ok');
set_pen_color(clRed);
draw_rectangle(30, 110, 70, 20);
l_c_bitmap:= tBitmap.Create;
l_c_bitmap.LoadFromFile('client_pc.bmp');
draw_bitmap(150, 110, l_c_bitmap);
  | |