menu
  index  ==>  papers  ==>  design_patterns  ==>  factory_and_bridge_patterns   

Abstract Factory and Bridge Design Patterns - Felix John COLIBRI.


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_controlClass(c_abstract_control)
                              m_x_1m_y_1m_x_2m_y_2Integer;

                              constructor create_square_abstract_control(p_nameString;
                                 p_c_control_list_refc_control_list;
                                 p_x_1p_y_1p_x_2p_y_2Integer);

                              function f_display_controlStringOverride;

                              function f_hit_test(p_xp_yInteger): BooleanOverride;
                              // -- 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_controlClass(c_square_abstract_control)
                            m_captionString;
                            constructor create_square_button_control(p_nameString;
                                p_c_control_list_refc_control_list;
                                p_x_1p_y_1p_x_2p_y_2Integer;
                                p_on_clickt_on_click);
                            procedure handle_control_mouse_down(p_xp_yInteger); Override;
                            procedure handle_control_mouse_up(p_xp_yInteger); Override;
                            procedure draw_controlOverride;
                          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_controlClass(c_abstract_control)
                             m_x_1m_y_1m_sizeInteger;
                             m_percentDouble;

                             constructor create_round_abstract_control(p_nameString;
                                p_c_control_list_refc_control_list;
                                p_x_1p_y_1p_sizeIntegerp_percentDouble);

                             function f_hit_test(p_xp_yInteger): BooleanOverride;

                             procedure draw_blank_rectangle;
                             procedure draw_round_rectangle(p_x_1p_y_1p_sizep_percentInteger);
                           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_downProcedure(p_xp_yIntegerof Object;
 t_on_clickProcedure of Object;

 c_control_listClass// forward

 c_abstract_controlClass(c_basic_object)
                       m_c_control_list_refc_control_list;

                       m_on_clickt_on_click;
                       m_on_mouse_downm_on_mouse_movem_on_mouse_upt_on_mouse_down;

                       Constructor create_abstract_control(p_nameString;
                           p_c_control_list_refc_control_list);
                       function f_display_controlStringVirtual;

                       function f_hit_test(p_xp_yInteger): BooleanVirtual;
                       procedure draw_controlVirtualAbstract;

                       procedure handle_control_mouse_down(p_xp_yInteger); Virtual;
                       procedure handle_control_mouse_move(p_xp_yInteger); Virtual;
                       procedure handle_control_mouse_up(p_xp_yInteger); Virtual;
                     end// c_abstract_control

 c_control_listclass(c_basic_object)
                    m_c_paintbox_reftPaintbox;
                    m_c_canvas_reftCanvas;
                    m_c_save_canvas_stackc_save_canvas_stack;

                    m_c_control_listtStringList;

                    Constructor create_control_list(p_nameString;
                         p_c_paintbox_reftPaintBox);

                    function f_control_countInteger;
                    function f_c_abstract_control(p_control_indexInteger): c_abstract_control;
                    function f_index_of(p_control_nameString): Integer;
                    procedure add_control(p_control_nameStringp_c_abstract_controlc_abstract_control);

                    procedure display_control_list;

                    function f_c_find_mouse_control(p_xp_yInteger): c_abstract_control;

                    procedure handle_mouse_down(p_xp_yInteger);
                    procedure handle_mouse_move(p_xp_yInteger);
                    procedure handle_mouse_up(p_xp_yInteger);

                    procedure draw_control_list;

                    Destructor DestroyOverride;
                  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_listc_control_listNil;

procedure TForm1.on_insert_button_click;
  begin
    display('insert');
    Panel1.Color:= ClRed;
  end// on_insert_button_click

procedure TForm1.create_square_world_Click(SenderTObject);
  var l_c_square_buttonc_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(SenderTObject);
  var l_c_round_buttonc_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_widgetClass(c_basic_object)
                           end// c_abstract_button_widget
 c_abstract_label_widgetClass(c_basic_object)
                          end// c_abstract_label_widget
 c_abstract_draw_surface_widgetClass(c_basic_object)
                                 end// c_abstract_draw_surface_widget
 c_abstract_scrollbar_widgetClass(c_basic_object)
                               end// c_abstract_draw_surface_widget

 c_abstract_widget_factory=
     class(c_basic_object)
        m_c_control_list_refc_control_list;

        Constructor create_abstract_widget_factory(p_nameString;
            p_c_control_list_refc_control_list);

        function f_c_create_button_widget(p_captionString;
            p_leftp_topp_widthp_heightInteger;
            p_on_clickt_on_click): c_abstract_button_widgetVirtualAbstract;
        function f_c_create_label_widget(p_captionString;
            p_leftp_topp_widthp_heightInteger): c_abstract_label_widgetVirtualAbstract;
        function f_c_create_draw_surface_widget(p_captionString;
            p_leftp_topp_widthp_heightInteger;
            p_on_mouse_downp_on_mouse_movep_on_mouse_upt_on_mouse_down): 
            c_abstract_draw_surface_widgetVirtualAbstract;
        function f_c_create_scrollbar_widget(p_captionString;
            p_leftp_topp_widthp_heightInteger): c_abstract_scrollbar_widgetVirtualAbstract;

        procedure create_lexi_world(p_leftp_topp_widthp_heightInteger);
      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_widgetClass(c_abstract_button_widget)
                           m_c_square_button_controlc_square_button_control;
                           Constructor create_square_button_widget(p_nameString;
                               p_c_abstract_widget_factoryc_abstract_widget_factory;
                               p_leftp_topp_widthp_heightInteger;
                               p_on_clickt_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_nameString;
            p_c_control_list_refc_control_list);

        function f_c_create_button_widget(p_captionString;
            p_leftp_topp_widthp_heightInteger;
            p_on_clickt_on_click): c_abstract_button_widgetOverride;
        function f_c_create_label_widget(p_captionString;
            p_leftp_topp_widthp_heightInteger): c_abstract_label_widgetOverride;
        function f_c_create_draw_surface_widget(p_captionString;
            p_leftp_topp_widthp_heightInteger;
            p_on_mouse_downp_on_mouse_movep_on_mouse_upt_on_mouse_down): 
            c_abstract_draw_surface_widgetOverride;
        function f_c_create_scrollbar_widget(p_captionString;
            p_leftp_topp_widthp_heightInteger): c_abstract_scrollbar_widgetOverride;
      end// c_abstract_factory

and the user will call:

procedure TForm1.create__Click(SenderTObject);
  var l_c_widget_factoryc_abstract_widget_factory;
      l_running_yl_running_xInteger;

  procedure add_button(p_captionStringp_widthIntegerp_click_eventt_on_click);
    begin
      with l_c_widget_factory do
        f_c_create_button_widget(p_captionl_running_xl_running_yp_widthk_button_default_height,
            p_click_event);
      Inc(l_running_xp_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_factoryc_abstract_widget_factory;
    l_c_button_widgetc_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_widgetc_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_widgetc_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_downu_c_control.t_on_mouse_down;

 c_abstract_gadget_implementation=
     Class(c_basic_object)
       m_c_control_list_refc_control_list;

       constructor create_abstract_gadget_implementation(p_nameString;
           p_c_control_list_refc_control_list);

       // -- label
       procedure set_label_caption(p_captionString); VirtualAbstract;

       // -- scrollbar
       procedure set_scrollbar_position(p_scrollbar_nameString;
           p_positionInteger); VirtualAbstract;
       function f_get_scrollbar_positionIntegerVirtualAbstract;

       // -- drawing
       procedure save_device_contextVirtualAbstract;
       procedure restore_device_contextVirtualAbstract;

       procedure set_pen_color(p_pen_colorInteger); VirtualAbstract;
       procedure set_brush_style(p_brush_styletBrushStyle); VirtualAbstract;

       // -- here placed the code at this level rather than duplicating
       // --   it for the demo
       procedure set_pen_mode(p_pen_modetPenMode); Virtual;
       procedure set_brush_color(p_brush_colorInteger); Virtual;
       procedure canvas_set_font_height(p_font_heightInteger); Virtual;
       function f_canvas_get_font_heightIntegerVirtual;

       procedure canvas_draw_bitmap(p_xp_yIntegerp_c_bitmaptBitMap); Virtual;

       function f_canvas_character_width(p_characterChar): IntegerVirtualAbstract;
       function f_canvas_character_height(p_characterChar): IntegerVirtualAbstract;

       procedure canvas_line(p_x_1p_y_1p_x_2p_y_2Integer); VirtualAbstract;
       procedure draw_rectangle(p_leftp_topp_widthp_heightInteger); VirtualAbstract;
       procedure device_text_out(p_xp_yIntegerp_textString); VirtualAbstract;

       procedure fill_rectangle(p_leftp_topp_widthp_height,
           p_colorInteger);  VirtualAbstract;
     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_refc_abstract_gadget_implementation;
       m_gadget_leftm_gadget_topm_gadget_widthm_gadget_heightInteger;

       m_on_mouse_downm_on_mouse_movem_on_mouse_upt_on_gadget_mouse_down;

       Constructor create_abstract_gadget(p_nameString;
           p_c_abstract_gadget_implementation_refc_abstract_gadget_implementation;
           p_gadget_leftp_gadget_topp_gadget_widthp_gadget_heightInteger);

       function f_display_gadgetStringVirtual;

       procedure save_graphic_context;
       procedure restore_graphic_context;
       procedure set_pen_color(p_colorInteger);
       procedure set_pen_mode(p_pen_modetPenMode);
       procedure set_brush_style(p_brush_styletBrushStyle);
       procedure set_brush_color(p_brush_colorInteger);
       procedure set_font_height(p_font_heightInteger);
       function f_get_font_heightInteger;

       function f_character_width(p_characterChar): Integer;
       function f_character_height(p_characterChar): Integer;

       procedure draw_line(p_x_1p_y_1p_x_2p_y_2Integer);
       procedure draw_triangle(p_leftp_topp_widthp_heightInteger);
       procedure draw_rectangle(p_leftp_topp_widthp_heightInteger);
       procedure draw_text(p_xp_yIntegerp_textString); Virtual;
       procedure shrink_gadget(p_delta_leftp_delta_topp_delta_rightp_delta_bottomInteger); Virtual;

       procedure fill_rectangle(p_leftp_topp_widthp_heightp_colorInteger);
       procedure draw_bitmap(p_xp_yInteger;  p_c_bitmaptBitmap);

       procedure draw_contentVirtualAbstract;
     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_gadgetclass(c_abstract_gadget)
                    Constructor create_button_gadget(p_nameString;
                        p_c_abstract_gadget_implementation_refc_abstract_gadget_implementation;
                        p_gadget_leftp_gadget_topp_gadget_widthp_gadget_heightInteger);
                  end// c_dialog_gadget

 c_label_gadget=
     class(c_abstract_gadget)
       Constructor create_label_gadget(p_nameString;
           p_c_abstract_gadget_implementation_refc_abstract_gadget_implementation;
           p_gadget_leftp_gadget_topp_gadget_widthp_gadget_heightInteger);
       procedure set_caption(p_captionString);
     end// c_dialog_gadget

 c_scrollbar_gadget=
     class(c_abstract_gadget)
       Constructor create_scrollbar_gadget(p_nameString;
           p_c_abstract_gadget_implementation_refc_abstract_gadget_implementation;
           p_gadget_leftp_gadget_topp_gadget_widthp_gadget_heightInteger);
       procedure draw_contentOverride;
       procedure shrink_gadget(p_delta_leftp_delta_top,
           p_delta_rightp_delta_bottomInteger); Override;
       procedure set_position(p_positionInteger);
       function f_get_positionInteger;
     end// c_scrollbar_gadget

 c_draw_surface_gadget=
     Class(c_abstract_gadget)
       Constructor create_draw_surface_gadget(p_nameString;
           p_c_abstract_gadget_implementation_refc_abstract_gadget_implementation;
           p_gadget_leftp_gadget_topp_gadget_widthp_gadget_heightInteger);
       procedure draw_contentOverride;
     end// c_draw_surface_gadge



And the concrete gadgets with some specific methods:

 c_button_gadgetclass(c_abstract_gadget)
                    Constructor create_button_gadget(p_nameString;
                        p_c_abstract_gadget_implementation_refc_abstract_gadget_implementation;
                        p_gadget_leftp_gadget_topp_gadget_widthp_gadget_heightInteger);
                  end// c_dialog_gadget

 c_label_gadget=
     class(c_abstract_gadget)
       Constructor create_label_gadget(p_nameString;
           p_c_abstract_gadget_implementation_refc_abstract_gadget_implementation;
           p_gadget_leftp_gadget_topp_gadget_widthp_gadget_heightInteger);
       procedure set_caption(p_captionString);
     end// c_dialog_gadget

 c_scrollbar_gadget=
     class(c_abstract_gadget)
       Constructor create_scrollbar_gadget(p_nameString;
           p_c_abstract_gadget_implementation_refc_abstract_gadget_implementation;
           p_gadget_leftp_gadget_topp_gadget_widthp_gadget_heightInteger);
       procedure draw_contentOverride;
       procedure shrink_gadget(p_delta_leftp_delta_top,
           p_delta_rightp_delta_bottomInteger); Override;
       procedure set_position(p_positionInteger);
       function f_get_positionInteger;
     end// c_scrollbar_gadget

 c_draw_surface_gadget=
     Class(c_abstract_gadget)
       Constructor create_draw_surface_gadget(p_nameString;
           p_c_abstract_gadget_implementation_refc_abstract_gadget_implementation;
           p_gadget_leftp_gadget_topp_gadget_widthp_gadget_heightInteger);
       procedure draw_contentOverride;
     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_nameString;
    p_c_abstract_gadget_implementation_refc_abstract_gadget_implementation;
    p_gadget_leftp_gadget_topp_gadget_widthp_gadget_heightInteger);
  begin
    Inherited create_abstract_gadget(p_namep_c_abstract_gadget_implementation_ref,
        p_gadget_leftp_gadget_topp_gadget_widthp_gadget_height);
  end// create_label_gadget

procedure c_label_gadget.set_caption(p_captionString);
  begin
    draw_text(m_gadget_leftm_gadget_topp_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_refc_control_list;
        _m_c_abstract_gadget_implementation_refc_abstract_gadget_implementation;

        Constructor create_abstract_gadget_factory(p_nameString;
            p_c_control_list_refc_control_list);

        function f_c_create_button_gadget(p_captionString;
            p_leftp_topp_widthp_heightInteger;
            p_on_clickt_on_click): c_button_gadgetVirtualAbstract;
        function f_c_create_label_gadget(p_captionString;
            p_leftp_topp_widthp_heightInteger): c_label_gadgetVirtualAbstract;
        function f_c_create_draw_surface_gadget(p_captionString;
            p_leftp_topp_widthp_heightInteger;
            p_on_mouse_downp_on_mouse_movep_on_mouse_upt_on_mouse_down):
            c_draw_surface_gadgetVirtualAbstract;
        function f_c_create_scrollbar_gadget(p_captionString;
            p_leftp_topp_widthp_heightInteger): c_scrollbar_gadgetVirtualAbstract;

        function f_c_create_gadget_implementation(p_nameString):
            c_abstract_gadget_implementationVirtualAbstract;

        procedure create_lexi(p_leftp_topp_widthp_heightInteger);
      end// c_abstract_gadget_factory

And here is the definition of the concrete square factories:

 c_square_button_gadgetClass(c_button_gadget)
                           m_c_square_button_controlc_square_button_control;
                           Constructor create_square_button_gadget(p_nameString;
                               p_c_abstract_gadget_factoryc_abstract_gadget_factory;
                               p_leftp_topp_widthp_heightInteger;
                               p_on_clickt_on_click);
                         end// c_square_button_gadget
 c_square_label_gadgetClass(c_label_gadget)
                          m_c_square_label_controlc_square_label_control;
                          Constructor create_square_label_gadget(p_nameString;
                              p_c_abstract_gadget_factoryc_abstract_gadget_factory;
                              p_leftp_topp_widthp_heightInteger);
                        end// c_square_label_gadget
 c_square_draw_surface_gadgetClass(c_draw_surface_gadget)
                                 m_c_square_draw_surface_controlc_square_draw_surface_control;
                                 Constructor create_square_draw_surface_gadget(p_nameString;
                                     p_c_abstract_gadget_factoryc_abstract_gadget_factory;
                                     p_leftp_topp_widthp_heightInteger;
                                     p_on_mouse_downp_on_mouse_move,
                                     p_on_mouse_upt_on_mouse_down);
                               end// c_square_draw_surface_gadget
 c_square_scrollbar_gadgetClass(c_scrollbar_gadget)
                               m_c_square_scrollbar_controlc_square_scrollbar_control;
                               Constructor create_square_scrollbar_gadget(p_nameString;
                                   p_c_abstract_gadget_factoryc_abstract_gadget_factory;
                                   p_leftp_topp_widthp_heightInteger);
                             end// c_square_scrollbar_gadget
 c_square_gadget_factoryclass(c_abstract_gadget_factory)
                            Constructor create_square_gadget_factory(p_nameString;
                                p_c_control_list_refc_control_list);

                            function f_c_create_button_gadget(p_captionString;
                                p_leftp_topp_widthp_heightInteger;
                                p_on_clickt_on_click): c_button_gadgetOverride;
                            function f_c_create_label_gadget(p_captionString;
                                p_leftp_topp_widthp_heightInteger): c_label_gadgetOverride;
                            function f_c_create_draw_surface_gadget(p_captionString;
                                p_leftp_topp_widthp_heightInteger;
                                p_on_mouse_downp_on_mouse_move,
                                p_on_mouse_upt_on_mouse_down): c_draw_surface_gadgetOverride;
                            function f_c_create_scrollbar_gadget(p_captionString;
                                p_leftp_topp_widthp_heightInteger):
                                c_scrollbar_gadgetOverride;

                            function f_c_create_gadget_implementation(p_nameString): c_abstract_gadget_implementationOverride;
                          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_gadgetc_button_gadget;
        g_c_label_gadgetc_label_gadget;
        g_c_scrollbar_gadgetc_scrollbar_gadget;
        g_c_draw_surface_gadgetc_draw_surface_gadget;

    procedure TForm1.create_bridge_Click(SenderTObject);
      var l_c_gadget_factoryc_abstract_gadget_factory;
          l_c_gadget_implementationc_abstract_gadget_implementation;
          l_memo_rectanglet_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_heightNil);
          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_leftm_width+ 5, m_top, 20, m_height);
            if surface_.Checked
              then g_c_draw_surface_gadget:= f_c_create_draw_surface_gadget('memo',
                  m_leftm_topm_widthm_height,
                  NilNilNil);
          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(SenderTObject);
      var l_c_bitmaptBitmap;
      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);