Welcome to Foxite.COM Community Weblog Sign in | Join | Help

FoxRockX – Time to Put up, or Shut up

Over the years much has been made about the FoxPro Community and how important it is to the life and survival of the product. However, as I have said many times (most recently in my "So, no more Visual FoxPro. Now what?" blog entry from May of 2007), it has often been noticeable that the "Community" has been long on talk and short on reaching for their wallets. 

Of course, in the past year Microsoft has announced that VFP will not get another version and that future work, if any, on the product will be limited to critical fixes only. Add to that, the fact that participation at conferences has fallen to the point where from having one major international conference and two or three regional conferences per year in the USA there is now just one regional conference dedicated to FoxPro left – SW Fox had over 150 attendees last year (out of how many thousand FoxPro users in the USA alone - where were the rest of them?) See http://www.swfox.net for details of the 2008 conference... 
Subscription to magazines has dropped off to the point that the two major monthly publications dedicated to FoxPro both essentially died on their feet. Participation in the technical on-line forums has dropped significantly over the past 10-12 months.

Into this picture, at this moment in time, steps Rainer Becker.  

Who is Rainer Becker? 
Rainer Becker is the leader of the German Foxpro user group DFPUG since 1993, publishes the small magazine Foxx Professional, runs an interactive Visual FoxPro forum at forum.dfpug.de, a Visual FoxPro document portal at portal.dfpug.de, a Visual FoxPro eNewsletter at newsletter.dfpug.de, an international online shop at shop.dfpug.com and has organized the German Devcon since 1994. He has been a speaker at various Microsoft events as the Dev Days, Fox Teach and Ce BIT - at Ce BIT fair he was at the VFP demo machine 1993-2005. In his spare time he undertakes consulting work for "Wizards Builders" and for "ISYS GmbH" as well as publishing the Visual FoxPro framework "Visual Extend".
A
dditionally he has been a Microsoft MVP for his work in promoting and supporting Visual FoxPro in Europe since 1997 and in 2007 was a recipient of the FoxPro Community Lifetime Achievement Award.

What has he done? 
First he has undertaken the enormous (and very risky) task of  starting a completely new bi-monthly magazine dedicated to Visual FoxPro – which will be available both on-line and in printed version. That is FoxRockX.
Second he has bought up the entire FoxTalk archive and is making it available, on-line to subscribers to the new magazine, at no additional charge!

You can find full details of FoxRockX here: http://www.foxrockx.com/seite.htm
And you can subscribe by going to either:
http://www.hentzenwerke.com (USA/Asia)
http://shopdfpug.com (Europe)

Of course the most important question for many people is, how much will it cost?
O
n-Line Subscription (including access to the archives)     US$ 99.00      ( 75 EUR )
Printed Subscription (includes on-line and archive)           US$ 158.00    ( 109 EUR )

(Note if you are an existing FoxTalk subscriber you can upgrade your existing on-line subscription to the printed version by paying only the difference i.e. US$ 59.00 (34 EUR).

Marcia and I have already bought our subscription to the printed version, we have an article in the first issue and plan to continue writing for FoxRockX as long as they will have us. 

However, the success of this venture is going to rely totally on the FoxPro community. If the community puts its money where its mouth is, it will succeed and could help bring a new lease on life for the product that we all use and love. If the community refuses to support the initiative then the effort will die, and I am very much afraid that all hope for FoxPro will die along with it.

So it is my opinion that this really is crunch time for the FoxPro community. Whatever you may think about the situation personally, either put up and support this international community-led effort, or shut up!

 

posted by andykr | 4 Comments

More on the Advantage Database Server

If you are interested in learning more about the advantage database server that I mentioned on this blog a couple of months ago, then you should visit J D Mullin's blog here:

http://jdmullin.blogspot.com/2008/02/getting-started-with-visual-foxpro-and.html

He has put together a very nice ScreenCast (about 10 minutes long) that shows how to get started and some of the things you can do with VFP and Advantage Database Server.

 

posted by andykr | 0 Comments

Form Event Sequences

One of the things that occasionally seems to confuse people is the question of the order in which the events happen when a form is instantiated. The basic answer, in terms of the Form's own events, is given by the acronym "LISA G", which stands for:

L => Load
I => Init ()each control first, then Form last)
S => Show
A => Activate
G => GotFocus (first control in the Tab order)
Actually you could argue that this should be "LISAR G" – because whenever the Activate() event of a form is triggered, a Refresh() is also initiated. Anyway, while these are the native form events, there are other things happening when a form is instantiated, most notably when a DataEnvironment is involved.

For a form  which uses a DE, and contains an exit button and pageframe with two pages the full initialization sequence is:

FORM.DATAENVIRONMENT.OPENTABLES
FORM.DATAENVIRONMENT.BEFOREOPENTABLES
FORM1.LOAD
FORM1.DATAENVIRONMENT.CURSOR1.INIT
FORM1.DATAENVIRONMENT.INIT
FORM1.CMDEXIT.INIT
FORM1.CMDREFRESH.INIT
FORM1.PGFMAIN.PAGE1.GRDCUSTOMER.COLUMN2.TXTCOL2.INIT
FORM1.PGFMAIN.PAGE1.GRDCUSTOMER.INIT
FORM1.PGFMAIN.PAGE1.INIT
FORM1.PGFMAIN.PAGE2.TB01.INIT
FORM1.PGFMAIN.PAGE2.CB03.INIT
FORM1.PGFMAIN.PAGE2.TB02.INIT
FORM1.PGFMAIN.PAGE2.INIT
FORM1.PGFMAIN.INIT
FORM1.INIT

The whole thing starts with the DataEnvironment OpenTables() which is an event that calls the BeforeOpenTables(), and then proceeds with the Form’s Load() event. After that the sequence is pretty much as expected. The objects contained by the form (Command Button and Pageframe) are initialized first, in the order in which they were added to the form, followed by the containers themselves, beginning with the DataEnvironment. This part of the sequence finishes with the Form’s Init().

This is, of course, why parameters passed to a form have to be received in the Init(). It is the first form method that fires after all the controls are available, but before any of them have actually been updated.

The next part of the process is concerned with updating the controls from their data sources (which is what the Refresh() method actually does) and then making the form visible, the events fire as follows:

FORM1.SHOW
FORM1.PGFMAIN.PAGE1.ACTIVATE
FORM1.ACTIVATE
FORM1.REFRESH
FORM1.CMDEXIT.REFRESH
FORM1.CMDREFRESH.REFRESH
FORM1.PGFMAIN.REFRESH
FORM1.PGFMAIN.PAGE1.REFRESH
FORM1.PGFMAIN.PAGE1.GRDCUSTOMER.REFRESH
FORM1.CMDREFRESH.WHEN
FORM1.GOTFOCUS
FORM1.CMDEXIT.WHEN

Notice, however, that only the active page (Page1) of the pageframe gets refreshed. This means that the controls on Page 2 have not yet been updated and this explains why you always need to do an explicit Refresh() when navigating to a new page.

There is one thing to watch here, though. If you simply add a "ThisForm.Refresh()" to the activate event of the first page of your pageframe you would actually end up calling it THREE times when initializing a form. Once (implicitly) from the Form.Activate(), a second time explicitly when the first page of the pageframe is refreshed and finally when the page itself is activated.

Note: This underlines just how important it is to avoid the (way-too-common) practice of calling ThisForm.Refresh() from all over the place. A form level refresh should really only be called when you are certain that it is needed. In other words, ThisForm.Refresh() should always be preceded by some sort of test!

One way to handle this is to create a wrapper method for the Form Refresh() and include a test for a form level property that is set by events that will require a form level refresh (like moving the record pointer, or changing the form's mode from "Add" to "View" for example). We use a method named "RefreshForm" to handle this and it looks like this:

LPARAMETERS tlSetObjMode
WITH Thisform
  IF .FormRefreshNeeded
    .LockScreen = .T.
    IF .BeforeRefresh()
      IF tlSetObjMode
        .SetObjMode( This )
      ENDIF  
      .Refresh()
      .AfterRefresh()
    ENDIF
  ENDIF  
  .LockScreen = .F.
ENDWITH 

The parameter is used to determine whether to re-evaluate the form's mode and change any object whose state depends on that mode (handled by the SetObjMode() method). Notice that we also have hooks to "Before" and "After" methods that allow us to modify the behavior, and even cancel a refresh (by returning .F. from BeforeRefresh()) if necessary. By always calling this RefreshForm() method instead of directly calling Refresh() we buy ourselves a lot of flexibility.

Incidentally, if you instantiate a form from a VCX instead of an SCX the event sequence is identical except, of course, that there are no dataenvironment events.

So what happens when we release a form? The answer is that it depends on on HOW the form is released.

There are basically three ways of killing a form. First you can explicitly call its Release() method (i.e. ThisForm.Release() ). Second, you can click on the ‘close’ button, which actually calls the form’s QueryUnload() method and third you can release the object reference to the form: ( i.e. RELEASE ThisForm)

[1] Form Release() called:

            R => Release
            D => Destroy (Form FIRST, then all contained controls in reverse of the Init order)
            U => Unload
[2] Close button used:

            Q => QueryUnload
            D => Destroy (Form FIRST, then all contained controls in reverse of the Init order)
            U => Unload
[3] Object Reference released (note that neither Release(), nor QueryUnload() are fired in this case):
            D => Destroy (Form FIRST, then all contained controls in reverse of the Init order)
            U => Unload
In each case, the form's DE does not get released until AFTER the form Unload - so tables are still available in the Unload as well as properties - but no controls.

There is a potential problem here though. Notice that the first event common to all three methods is the Destroy(). However, once the Form's Destroy fires there is no getting out of it, even a NODEFAULT will not stop the release process. That means that there is no single native method or event in which you can place code that checks to determine whether a form should be released or not. (It also means that if you release the object reference, it absolutely will be killed - there is no way to undo such this action because it fires the Destroy() directly).

If you do need to check that it is Ok to release a form you will either have to disable the 'close' button on all your forms, or place the checking code in a custom method that is called from BOTH the form's Release() and QueryUnload() events because a NODEFAULT in either of these methods will halt the destruction of the form. This code, in both events, will do the trick:

IF NOT ThisForm.OK2Destroy()
  NODEFAULT
ENDIF

The most usual reason for this sort of code is to prevent users from closing a form with uncommitted changes pending, so the OK2Destroy() will typically call the form's CheckForChanges() method. As usual we prefer to call 'control methods' that then call the actual 'action' methods so that if we do need to modify behavior there is a place to do it from.

posted by andykr | 4 Comments

Advantage Database Server V9.0 released as Beta

Sybase Anywhere have been working on a Visual FoxPro compatible version of the Advantage Database Server for some time and, just before Christmas, the long-awaited Version 9.0 was released in Beta. You can download the Beta version (time limited until the end of March 2008) from here:

http://devzone.advantagedatabase.com/dz/content.aspx?Key=20

 

For a full version you will need to download the Server, the Data Architect and the Advantage OLEDB and ODBC drivers.

 

Now some of you may be wondering why this should be news, or maybe you have never heard of the Advantage Database server. There is a lot of very good information on this neat little tool on their web site and I have been very impressed with it but there are a couple of things that should make it of immediate and very real interest to all VFP Users:

 

[1] The ODBC driver will access all current VFP Data Table and Data Types. Yes, including Auto-Inc, Varchar() and all the newest features. For any of you who have been unable to move off VFP 6.0 because you need to access your data through ODBC there is now a solution!

 

[2] The advantage server can be used to connect directly to DBF files, replacing the DBC with a true multi-threaded, multi-user, remote server that includes, among many other features, a SQL Debugger (yes, you can step through SQL Queries one line at a time!), replication and merge functionality, and that runs on Windows, Linux or Netware.

 

I have been playing with this version for a couple of weeks now and have been very impressed by its capabilities and potential. I am not going to try and cover all the things that the Advantage Database Server can do, but perhaps the one thing that is worth mentioning is that it can remove the necessity for users to have direct access to your DBF files. This requirement has long been one of the major objections to the DBF file as a data store because of the ease with which these files can be read and even corrupted. The Advantage Database Server makes this direct access unnecessary. DBF Files can now be hidden away from prying eyes (only the server needs access) and the server also manages locking and concurrency issues.

 

Coming so soon on the news that Microsoft are abandoning VFP to its fate, it is nice to see that someone out there thinks that there is still some value in the product and is willing to put some effort into it.

 

Here is what the Help File has to say about VFP support:

Visual FoxPro 9 File Format Support

Advantage support for FoxPro DBF tables and CDX/IDX index files has been enhanced to include all data types supported by Visual FoxPro 9.  This includes support for Auto-increment, BLOB (Binary Large Objects), Currency, DateTime (timestamp), VarBinary, and VarChar field types. 
Null field support has also been added to give true SQL-style NULL handling for Visual FoxPro DBF tables.  Support for true unique (candidate) indexes is now also available on DBF tables; this allows for better SQL optimization, the definition of primary keys, and the creation of referential integrity rules. 
Additionally, support for long field names with DBF tables has been added.  In addition to supporting the robust and high-performance Advantage proprietary locking on the new table format, Advantage also supports Visual FoxPro compatibility locking, which allows you to use Advantage-enabled applications to share tables with existing Visual FoxPro applications that are not using Advantage.

Visual FoxPro Upsizing Utility

 To complement the enhanced support for Visual FoxPro tables, this release includes an upsizing utility that aids in updating your application to use your existing Visual FoxPro tables with Advantage Database Server. 
It is not necessary to update your tables or indexes to allow them to be used with Advantage Database Server.  However, the upsizing utility will export much of the information in your Visual FoxPro Database Container (.DBC) to an Advantage Data Dictionary (.add) to allow support for features such as long field names, primary keys, referential integrity, and triggers. 
This utility, written as a Visual FoxPro application that you can modify, currently has the ability to export view definitions, referential integrity rules, default field values, field and table validation rules, and primary keys.

 

If you are still not sure what this is all about, here is an extract from the Overview section of the (very comprehensive) help file for Version 9.0:

 

Advantage Database Server is a high performance client/server RDBMS for stand-alone, networked, Internet, and mobile database applications. Advantage Database Server allows developers the flexibility to combine powerful SQL statements and relational data access methods with the performance and control of navigational commands. Advantage has native development interfaces designed to leverage existing knowledge of popular development tools. With optimized data access methodology for easily delivering unparalleled performance, Advantage provides security, stability, and data integrity while being completely maintenance-free.
The Advantage Database Server is the key to improved database performance in network environments. The server can be visualized as an intelligent controller that reduces competition for resources and off-loads much of the work normally performed by each client workstation. It is responsible for all database access, including all reading and writing of data, and lock management. Working with the network operating system, the Advantage Database Server processes data requests and returns the information to the network clients.
The Advantage Database Server supports the NetWare, Windows, and Linux operating systems. The Advantage Database Server for NetWare is implemented as a NetWare Loadable Module (NLM). An NLM is simply an executable for the NetWare operating system. The Advantage Database Server for Windows operates as a Windows Service.

Note  The Advantage Database Server for Windows is a Service. It cannot be run as a standard Windows application. See Installing and Starting the Advantage Database Server for Windows , for more information on Windows Services and how to start them.

The Advantage Database Server retrieves requests for database operations to be performed on behalf of the clients. The Advantage Database Server locates tables on the server and processes the database operations. The result of the operation is then returned to the client across the network, eliminating the need to send the database to the client for processing. This provides far better concurrency control and system integrity than is otherwise available.
Traditional non-client/server applications send raw data from the server across the network to be processed on the workstation. With the Advantage Database Server, much of the data is processed by the Advantage Database Server on the file server. By decreasing network traffic, you increase performance.
The Advantage Database Server integrity system ensures that database updates either run to completion or do not begin. The Advantage Database Server will not execute partial commands. This means that the integrity of your database no longer depends on the stability of the workstations on the network. Because the Advantage Database Server is responsible for all database access (on behalf of the clients), it can do a far better job of concurrency control than traditional systems, where concurrency must be synchronized between remote workstations. Better concurrency control means better multi-user performance.

 

What we have still to find out is what the new version will cost! But if you are looking for ways to leverage your VFP data and applications without the costs involved in porting to SQL Server or MySQL, this may be something that you should take a long hard look at - for the next couple of months at least it will be freely available for download and evaluation.

 

 

posted by andykr | 0 Comments

Frankfurt Developer Conference is just 1 Week Away

By now I am sure you will have seen the rave reviews for SW Fox held last week in Mesa, Arizona. Rick, Doug and Tamar put together a fabulous conference which was really positive and upbeat. I have not been to such a great VFP conference in the US in many years!

However, for those of you who missed it, there is still time to get to the 14th Microsoft Visual FoxPro Developer Conference 2007 to be held  between 8/10th November in Frankfurt, Germany.

Apart from the setting, the food, the free beer and the warmth of the welcome, the Speakers for the English Track list is impressive including:

  • Marcia Akins
  • Craig Bernston
  • Steven Black
  • Yair Alan Griver
  • Doug Hennig
  • Vennelina Jordanova
  • Andy Kramek
  • Rick Schummer

For just 999 Euros (less than £700!) you get the full conference registration indcluding meals, (and WHAT meals they are).  There are special conference rates at the hotel, and the flights are really good value at this time of year too.

The range of topics available is extensive and there is no doubt that it is great value for money. You can find details here: http://devcon.dfpug.de/ and an English Registration form is at http://portal.dfpug.de/dFPUG/Dokumente/Konferenzen/Konferenzprogramm2007/Anmeldeformular%20engl.doc

For reviews of previous Frankfurt Devcons check out the Wiki at http://fox.wikis.com/wc.dll?Wiki~GreatThingsAboutDevconGermany~VFP

Hopefully a few more of my countrymen will make the effort this year and I look forward to seeing you there. (Apart from anything else, it is always embarassing when I am asked why I am the only Brit in the place Smile [:)])

posted by andykr | 0 Comments

Creating Classes with a Factory

This implementation uses the abstract factory pattern to avoid one of the most difficult code maintenance issues. This is the problem that arises whenever we need to change either the class name, or the source library (or both) from which we want to instantiate an object, or simply need to make changes to an existing class definition.

To change a name is fraught with peril and the only way to do it is to search the source code for all occurrences of the item that we want to change and replace it with the new one. This works just fine providing that we don’t miss one, or mistype either the name (or library, or both!) The more occurrences we have to fix the more likely it is that a mistake will be made – even using tools like “Code References”.

Even routine maintenance and enhancements can cause problems, especially when a team of developers is involved because a class library cannot be shared. So there is always a danger that changes made by one person get overwritten by changes in a different class made by another.

The implementation described here addresses both of these issues.

What is a Factory?

A factory class is basically one whose function is to instantiate other classes. It is an example of the more general ‘Abstract Factory’ design pattern, which is defined in “Design Patterns – Elements of Reusable Object-Oriented Software” (Gamma, Helm, Johnson & Vlissides, Addison Wesley 1997) as:

Provide an interface for creating families of related or dependent objects without specifying their concrete classes

What are the components of the factory?

The factory pattern requires a minimum of two components and, usually three (See Figure 3).

  • The Client – the object that requires an instance of a class. This is where, in conventional code, the CREATEOBJECT() (or NEWOBJECT()) function call would normally be located
  • The Factory - this is usually instantiated as a stand-alone object at application startup, although it can also be implemented as a method on an Application Object.
  • The Metadata - this, is what makes this implementation so useful in my opinion, and why VFP is such an ideal tool in which to implement this particular pattern

Figure 1: Abstract Factory Components

When should I use a Class Factory?

The short answer is always! There is really no reason not to use a factory to instantiate objects and the only real question is whether it should be implemented as a global object in its own right, or as a method on some other (i.e. Application) object. My personal preference is to create the factory as a separate object because I always use metadata to drive it, and it helps keep things tidy if the factory can run in its own private datasession.

The key thing to remember is that the role of the metadata in the abstract factory is to separate the functionality from the information that is used to drive it. In this case we are concerned with instantiating objects which would normally be handled explicitly by using code like this:

loObject = CREATEOBJECT( ‘MyClass’ )

OR

loObject = NEWOBJECT(  ‘MyClass’, MyClassLibrary’ )

As noted above, problems arise with this if either the class name, or the source library changes, or even if we want to test a new version of a class because we must either change the original class definition, or change the calling code. Either way we have to update, recompile and re-distribute the application. The solution is that, instead of hard coding the instantiation directly into the application, we hand the task off to another object.

In order to do that we need to associate a non-definitive “key” (that will be used in the code) with the actual class, and library names that can be looked up at run time to instantiate the correct class. Since the interpretation of the key name is handled inside the factory, there is never any need to amend the source code when changes to either class, or class library, are made. All that is needed is to change the metadata.

Implementation

The metadata table for the factory class is shown in Table 1. Notice that the metadata, in addition to the Key, Class and Library names includes a memo field which we use to define default values for specific properties by listing them as Attribute/Value pairs. There is also a logical field which allows us to disable particular classes when necessary (more on that later).

Table 1: Factory Class Metatdata

The factory class itself is defined programmatically using the session base class so that it always runs in its own private datasession. This obviates any possibility that the tables it uses will be affected by the application itself. The interface is very simple as shown in Table 2:

Table 2: Factory Class Interface

In practice we define TWO identical tables. The first is named “classes.dbf” and is the production version of the table, the second is named “wipclasses.dbf” (for Work-In-Progress). The code in the factory always checks for the key in the wipclasses table first so that we don’t actually have to modify things on the production side (classes.dbf) until we are sure that the new version is correct.  The consequence is that new classes can be tested by the developer, actually in the EXE, without re-compiling the code. All that is needed is an active entry in wipclasses.dbf to override the current ‘production’ version.

Another consequence is that, in a team environment, each developer can have their own local wipclasses table, and a read-only copy of the main classes.dbf. That way when someone is developing a class they don’t affect everyone else’s code while they are de-bugging their changes. Once a new class, or sub-class, is ready for production, all that needs to be done is to update the appropriate class library and (if needed) modify the entry classes table and the application begins using the new class immediately.

The factory class is defined in code as a sub-class of the Session base class. This, as noted above, means that the factory can be instantiated in the application environment as a global object that maintains its own private datasession. The class includes a property (lWIPTable) this serves as a flag that is set when the class is instantiated so that we don't have to keep testing for the presence of the WIP table. The Init() method is responsible for setting up the metadata, and setting this flag.

All of the real work is handled in the exposed New() method. It accepts a key name, and up to five additional parameters which (if present) are passed directly to the Init() of the object. If the object is instantiated correctly, a reference to the new object is returned to the client, otherwise a NULL value is handed back.

Note that the class determines, from the extension of the specified class library, whether to use CREATEOBJECT() if the library has been explicitly declared, or NEWOBJECT() if not. If the object is instantiated successfully, and there are settings defined as defaults in the Properties column, then the factory will attempt to set those values as well (see the GetProperties() and Str2Exp() methods) – this is, of course, in addition to any parameters that may be passed and handled by the class code directly.

So to create an instance of an object defined in the application source code all that has to be done is for the application to have access to the factory object and to call its New() method with the key name. The return value will either be an object reference or NULL, and the client handles both possibilities:

TRY
  loObject = oFactory.New( ‘Required_Object_KeyName’ )
  IF ISNULL( loObject )
    ERROR “Unable to instantiate” + Required_Object_KeyName )
  ENDIF
CATCH TO loErr
  MESSAGEBOX( loErr.Message + “ in ” + loErr.Procedure, 16, “Factory Error” )
ENDTRY

You may be wondering why this does not create a dangling object reference. The reason is simply that the object is created as LOCAL inside the Factory.New() method. So when the method completes, the local reference goes out of scope and is released, leaving the returned reference as the only one in existence.

Summary

The implementation of the factory pattern illustrated here offers two major benefits. First, and most obviously, it avoids the issues that arise when class and library names are embedded directly in code. Second, it allows developers to work on, and test in a production environment, code that is still ‘work-in-progress’ without breaking anyone else’s version of the application.

The code for our factory class is here:

******************************************************************************************
*** Program: Factory.prg
*** Written by Andy Kramek & Marcia G. Akins
*** Abstract: Abstract factory responsible for object instantiation
*** Parameters: keyword from classes table to look up class info and up to 4 optional parameters
*** Compiler.: Visual FoxPro 09.00.0000.3504 for Windows
*****************************************************************************************
DEFINE CLASS factory AS Session
*** Give it a private data session so we can set exact ON
*** so we can accurately use a SEEK into the classes table
DataSession = 2
lWIPTable = .F.
******************************************************
FUNCTION Init()
******************************************************
  *** This is a private data session so just force exact ON
  SET EXACT ON
  *** Open the classes table 
  IF NOT USED( 'Classes' )
    USE Classes AGAIN IN 0
  ENDIF
  *** Open WipClasses if we have it available, and set property
  IF NOT USED( 'WIPclasses' )
    IF FILE( 'WIPclasses.dbf' )
      USE WIPclasses AGAIN IN 0
      This.lWIPTable = .T.
    ENDIF
  ELSE
    This.lWIPTable = .T.
  ENDIF
ENDFUNC
 
****************************************************************************
FUNCTION New( tcKey, tuParam1, tuParam2, tuParam3, tuParam4, tuParam5 )
*****************************************************************************
LOCAL lcLibType, lcCommand, lnParm, lnParmCount, loObject, llFound, lcKey, lnSelect, llCreate, lcProperties
LOCAL lcLibrary, lcClassName
 
*** Make sure we got a keyword
IF EMPTY( tcKey )
  RETURN .NULL.
ENDIF
 
*** Get the class information
lcKey = UPPER( ALLTRIM( tcKey ) )
 
*** Check to see if the developers are using a local classes table
*** to test work in progress. If there is one, use the information
*** from the local table  if it is there. Only check the application
*** classes table if the keyword can't be found in the local one
lnSelect = SELECT()
IF This.lWIPTable
  SELECT WIPClasses
  llFound = SEEK( lcKey, 'WIPclasses', 'cKey' ) AND WipClasses.lActive
ENDIF
   
*** Now check the application classes table if
*** we need to
IF NOT llFound
  SELECT Classes
  llFound = SEEK( lcKey, 'Classes', 'cKey' ) AND Classes.lActive
ENDIF
   
IF NOT llFound
  *** Make sure keyword was found in classes table
  RETURN .NULL.
ENDIF
 
*** Save pertinent info and force to upper case
lcProperties = ALLTRIM( Properties )   
lcLibrary = UPPER( ALLTRIM( cLibrary ) )
lcClassName = UPPER( ALLTRIM( cClassName ) )
 
*** Is this class in a vcx or a prg?
lcLibType = This.ChkLibType( lcLibrary )
   
*** Make sure our class library has an extension
*** Just in case one was not specified in Classes.dbf
IF EMPTY( lcLibType )
  RETURN .NULL.
ELSE
  lcLibrary = FORCEEXT( lcLibrary, lcLibType )
ENDIF
 
*** Now, see if our prg or vcx has been set
IF lcLibType = 'PRG'
  llCreate = ( lcLibrary $ SET( 'PROCEDURE' ) ) OR ;
    ( FORCEEXT( lcLibrary, 'FXP' ) $ SET( 'PROCEDURE' ) ) 
ELSE
  llCreate = lcLibrary $ SET( 'CLASSLIB' )
ENDIF
 
IF llCreate
  lcCommand = 'CreateObject( "' + lcClassName + '"'
ELSE
  lcCommand = 'NewObject( "' + lcClassName + '", "' + lcLibrary + '"'
ENDIF
 
*** Now tack the parameters on to the end of the command
*** if any were passed 
lnParmCount = PCOUNT() - 1
IF  lnParmCount > 0
  *** Only add the third parameter if we are using NewObject
  IF NOT llCreate
    lcCommand = lcCommand + ', ""'
  ENDIF
  FOR lnParm = 1 TO  lnParmCount
    lcCommand = lcCommand + ', tuParam' + TRANSFORM( lnParm )
  ENDFOR
ENDIF
lcCommand = lcCommand + ' )'
 
*** Go ahead and instantiate the object
loObject = &lcCommand
 
*** Now see if we have some properties to set
IF NOT EMPTY( lcProperties )
  *** First make sure we have an object
  IF VARTYPE( loObject ) = 'O'
    This.GetProperties( loObject, lcProperties )
  ENDIF
ENDIF
 
SELECT ( lnSelect )   
 
RETURN loObject
         
ENDFUNC
 
*****************************************************************************
PROTECTED FUNCTION GetProperties( toObject, tcProperties )
*****************************************************************************
LOCAL lnTotal, laProps[ 1 ], lnCnt, lcPropName, lcPropValue, lcType, loItem, luValue
 
*** get all the attribute/value pairs in the memo field into a single array element
lnTotal = ALINES( laProps, tcProperties ) 
*** And process each pair in the array
FOR lnCnt = 1 TO lnTotal
  *** Get the name of the property
  lcPropName = ALLTRIM( GETWORDNUM( laProps[ lnCnt ], 1, '=' ) )
  lcPropValue = ALLTRIM( GETWORDNUM( laProps[ lnCnt ], 2, '=' ) )
  *** Make sure it is a property on the object
  IF PEMSTATUS( toObject, lcPropName, 5 )
    *** See if we can get its data type
    loItem = 'toObject.' + lcPropName
    lcType = TYPE( loItem )
    *** Now get back the value in the appropriate data type
    luValue = This.Str2Exp( lcPropValue, lcType )
    &loItem = luValue   
  ENDIF
ENDFOR
 
********************************************************************************
PROTECTED FUNCTION Str2Exp( tcExp, tcType )
********************************************************************************
LOCAL lcExp, luRetVal, lcType, lcStr
 
*** Verify parameters
IF VARTYPE( tcExp ) # 'C'
  ASSERT .F. MESSAGE TRANSFORM( tcExp ) + ' is NOT a character expression and you MUST pass a character expression to Str2Exp!'
  RETURN tcExp
ENDIF
IF EMPTY( tcType )
  ASSERT .F. MESSAGE 'You MUST pass a data type to Str2Exp!'
  RETURN tcExp
ENDIF
 
*** If no type passed -- map to expression type
lcType = UPPER( ALLTRIM( tcType ) )
*** Remove any NULL characters, and leading/trailing spaces
lcExp = CHRTRAN( ALLTRIM( tcExp ), CHR( 0 ), '' )
*** Convert from Character to the correct type
DO CASE
  *** Integers
  CASE INLIST( lcType, 'I', 'N' ) AND INT( VAL( lcExp ) ) == VAL( lcExp )
    luRetVal = INT( VAL( lcExp ) )
  *** Other Numeric
  CASE INLIST( lcType, 'N', 'B' )
    luRetVal = VAL( lcExp )
  *** Currency
  CASE lcType = "Y"
    luRetVal = NTOM( VAL( lcExp ))
  *** Character or memo
  CASE INLIST( lcType, 'C', 'M' )
    *** Remove delimiting marks if present.
    IF INLIST( LEFT(lcExp,1), CHR(91), CHR(34), CHR(39))
      *** We begin with a delimiter
      lcExp = SUBSTR( lcExp, 2 )
      *** So we should end with a delimiter
      IF INLIST( RIGHT(lcExp,1), CHR(93), CHR(34), CHR(39))
        lcExp = LEFT( lcExp, LEN( lcExp )- 1 )
      ENDIF
    ENDIF
    luRetVal = lcExp
  *** Logical
  CASE lcType = 'L'
    luRetVal = IIF( !EMPTY( CHRTRAN( lcExp, 'Ff0.', "" ) ), .T., .F.)
  *** Date
  CASE lcType = 'D' && Date
    *** Check for separators in the string
    IF CHRTRAN( lcExp, "/.-", "" ) == lcExp
      *** We are in yyyymmdd format
      lcStr = LEFT( lcExp, 4) + "," + SUBSTR( lcExp, 5, 2 ) + "," + RIGHT( lcExp, 2)
      luRetVal = DATE( &lcStr )
    ELSE
      *** We are in DTOC() format
      luRetVal = CTOD( lcExp )
    ENDIF
  *** DateTime
  CASE lcType = 'T' && DateTime
    *** Check for date separators in the string
    IF CHRTRAN( lcExp, "/.-", "" ) == lcExp
      *** No separators so we have something other than TTOC() format
      IF LEN( lcExp ) > 8
        *** This one must be in yyyymmddhhmmss format
        *** So get the date part first
        lcStr = LEFT( lcExp, 4) + "," + SUBSTR( lcExp, 5, 2 ) + "," + SUBSTR( lcExp, 7, 2)
        *** and convert to the correct date string format
        lcStr = DTOC( DATE( &lcStr ))
        *** Now tack on the hours part
        lcStr = lcStr + " " + SUBSTR( lcExp, 9, 2 )
        *** Minutes
        IF LEN( lcExp ) > 10
          lcStr = lcStr + ":" + SUBSTR( lcExp, 11, 2 )
        ELSE
          lcStr = lcStr + ":00"
        ENDIF
        *** Seconds
        IF LEN( lcExp ) > 12
          lcStr = lcStr + ":" + SUBSTR( lcExp, 13, 2 )
        ELSE
          lcStr = lcStr + ":00"
        ENDIF
        luRetVal = CTOT( lcStr )
      ELSE 
        *** This must be a date in yyyymmdd format which we want to force to DateTime format
        lcStr = LEFT( lcExp, 4) + "," + SUBSTR( lcExp, 5, 2 ) + "," + RIGHT( lcExp, 2)
        luRetVal = DTOT( DATE( &lcStr ))
      ENDIF
    ELSE
      *** We are already in TTOC() format
      luRetVal = CTOT( lcExp )
    ENDIF
 
  OTHERWISE
    *** We have an invalid combination of value and data type
    MESSAGEBOX( "Cannot convert " + lcExp + " to Data Type " + tcType, 16, "Conversion Failed " )
    luRetVal = lcExp
ENDCASE
*** Return value as Data Type
RETURN luRetVal
 
*****************************************************************************
PROTECTED FUNCTION ChkLibType( tcLibrary )
*****************************************************************************
LOCAL lcLibType
 
*** Checks for file extension on library name, and
*** figures out what it should be if not supplied
lcLibType = UPPER( JUSTEXT( tcLibrary ) )
 
IF NOT EMPTY( lcLibType )
  lcLibType = IIF( FILE( tcLibrary ), lcLibType, '' )
ELSE
  *** See if we have a vcx here
  lcLibType = IIF( FILE( FORCEEXT( tcLibrary,'VCX' ) ), 'VCX', '' )
  IF EMPTY ( lcLibType )
    lcLibType = IIF( FILE( FORCEEXT( tcLibrary,'PRG' ) ), 'PRG', '' )
  ENDIF
ENDIF   
   
RETURN lcLibType
 
ENDDEFINE


posted by andykr | 0 Comments

SW Fox, An Endangered Species I fear!

Marcia and I have just returned from Prague, where we were lucky enough to be among the speakers for the third time. This was, and is, a great conference and this year had over 300 attendees.

However, the first time we went there (in 2002) they had 600+. Why do I mention this?  Well, I am told that Advisor Devcon this year had about 35 attendees for the VFP Track (contrast that with Advisor DevCon 2000 in Miami where there were over 1200, and with Orlando in 1998 where there were more than 2000).

I also know that last year's Southwest Fox in Phoenix had only just over 100 attendees.

It does seem to be a general phenomenon across the world that fewer and fewer people are going to IT Conferences these days and there are probably several reasons for this. Obvious ones are budgetary constraints and increased pressure on deadlines that reduces 'training' time. However,  I also think that the ever-expanding scope of freely available advice and help on line has, for some people (especially those not directly involved), reduced the perceived value of conferences.

However I totally disagree with that viewpoint and consider that conferences offer three huge benefits that cannot be obtained in any other way:

 

[1] Networking.

In our business, as in any other, "what you know" is important, but often "who you know" is at least as important. How can you know someone unless you meet with them, talk with them and spend time with them? In some ways the actual conference sessions are the least important aspect of a conference. It is the opportunity to meet others who live in the same world as you do and who do the same things that you do, to talk with them and discuss common issues and problems that adds significant value.

Of course you also have to make the effort of going and talking to people! I have never understood people who go to a conference, attend the sessions and then disappear back to their hotel room – they are missing the most important part – the evening chat sessions in lobby, or the bar, or wherever the VFP crowd are congregating

 

[2] Broadening Horizons.

We each live our professional lives immersed in the day to day issues with which we are concerned. How often do we take the time out to investigate some new facet of VFP? Or to look at some programming technique or issue for which we have no immediate use? Not very often I would wager. This is the shortcoming that on-line forums and help sites cannot address. They are 'problem oriented' in that when a question is asked, the response is an answer to that question. What you do not get is the answers to unasked questions or general information.

For example, in Prague this year I attended a session given by Alan Griver on "VB Futures". This is something that I would not normally spend time researching, or even reading casually about, but it was an immensely interesting and revealing session and I learned a lot. Will I use it 'today'? No! Is it worth knowing about? Definitely!

 

[3] Learning New Skills/Approaches

One of the most significant benefits that I see from conferences is the presentation of alternatives. VFP is an immensely rich and powerful language and no-one really knows it all. (There are over 1500 Commands, Functions, Properties, Events and Methods, and probably 10 times that number of variations on them). It is almost an axiom that there are at least two ways (and usually more) of doing anything in VFP. What most of us (including myself) do is learn one and stick with it. At conferences I get a chance to see other approaches to problems, other techniques for using VFP and its tools and other people's solutions to problems.

For example, at Prague last week I was asked by someone whether I thought he should use BINTOC() when creating an index on DELETED() so as to minimize the index size and improve transmission over the network. This was something I had never considered, and I told him I didn't know. So we went and tried it out then and there and, you know what, it works fine! So there was something I learned that despite my more than 20 years working with Fox I had never come across before and about which I would never have dreamed of asking on line.

 

What is the point of this blog? Simply this;

 

This year we will have only one dedicated VFP Conference in the USA (Fox Forward, although VFP oriented,  is, intentionally, much broader in scope) and I greatly fear that it could very well be the last ever 'true' VFP Conference on this side of the Atlantic unless people in the community show their support in a practical way – by going to Phoenix in October!

 

The conference is being held at the Arizona Golf Resort and Conference Center, Mesa, Arizona and starts with a keynote presentation at 7:30pm on Thursday 18 October. Formal sessions begin at 8:00am on Friday 19 October, finishing at  1:00pm on Sunday 21 October.  

In addition there are a number of 'pre-conference' sessions (3-hour workshops) being held on Thursday 18 October.

 

For full details, and costs, go NOW to http://www.swfox.net/home.aspx and REGISTER

 

And remember, if we don't use it, we'll lose it - there may not be a "next year" unless we can all make "this year" a success

posted by andykr | 6 Comments

So, no more Visual FoxPro. Now what?

It has been over a month since Microsoft formally announced that there would be no new version of Visual FoxPro and that support for the current version would be retired in 2015. The reaction from large parts of the community was, of course, entirely predictable. Oh my Gosh! The sky is falling!

But what is the reality?

I have been deliberately silent on this topic to try and give the dust some time to settle and, also to take stock of my own position and intentions. So here, for what it's worth, is what I think about it all.

Emotionally, I am sad that Microsoft have decided not to invest in further development of Visual FoxPro. It is truly a great tool, way better than most people outside the dedicated user community, realize. It has been a mainstay of LAN based, desktop application development in all sorts of businesses for more than 20 years and as I (and many others) have been saying for years, it is also one of the best, if not the very best, tool for middle tier component development for use with all types of data-centric applications.

However, I also recognize that Microsoft is a business. I suspect that the demise of Visual FoxPro has more to do with the FoxPro community than most people in that community would like to admit. It has been made abundantly clear, on numerous occasions over recent years, that Visual FoxPro was not paying its way. The main cause is, of course, that the Royalty Free distribution, and lack of database licensing, give it a poor (i.e. non-existent) on-going revenue model. The only real revenue from VFP is, therefore, sales of new versions and when you couple this with the reluctance of the FoxPro Community to actually go out and buy new versions (why else is VFP 6.0 still the most widely used version of Visual FoxPro?) it is not hard to see why Microsoft are not keen on tackling a major re-write of Visual FoxPro.

Now hang on there; where did that come from?  Who said anything about a "major re-write"?

Well, if you think about it, what else could we be talking about for a Version 10?  There are two key issues to consider here.

First there is the question of 'features'. What new features could be added to Visual FoxPro in order to create a new version? Sure there are some little 'nice-to-have' things that could be added, but really the only things lacking in VFP are things that other Microsoft Products already provide. Even assuming it were possible, adding database features like integrated Security, Transaction Logging or extending the table size beyond the 2Gb limit, would only make Visual FoxPro a competitor for SQL Server. Similarly adding development features, like native Web Page capability, or CLR integration would only make it compete with Visual Studio. Why on earth would Microsoft want to do things like this for a product on which they don't make any money anyway?

Second there is the issue of technology. Whether we like it or not Visual FoxPro is bedded firmly in the technology of the 1980s and 90s. "Backward Compatibility' has always been one of Visual FoxPro's major strengths, but this is partly because it hasn’t really changed its core technology that much over the years. Now we are confronted with 64-bit, multiple processor hardware with more RAM than one would have dreamed of 5 years ago. Visual FoxPro was not built to run in such environments and changing it so that it can take advantage of the technology would surely be difficult and expensive. In fact I doubt if anything short of a total re-write from the ground up would suffice.

So while I may not like the decision, I fully understand it. In fact, I am grateful that Microsoft have agreed to stand by the product until 2015 anyway. Not only that, they have committed to delivering Service Pack 2, and the Sedna patch to VFP 9.0 (that will enable VFP to run under, and take advantage of, the next generation of operating systems).

So, now that we are over that, what next?

Well the first thing to consider is what does the announcement actually mean. 

Microsoft have stated that VFP will not stop working – merely that they will not continue to support the product after 2015. This is perfectly normal and in line with their standard practice. Take a look at the product life cycle pages for any Microsoft Development tool and you will see the same pattern. You can find the web page here:

http://support.microsoft.com/default.aspx/gp/lifeselectdevtools

Here are the dates for all the versions of Visual FoxPro since VFP 6.0:

Version

Released

Mainstream Support Retired

Extended Support Retired

Visual FoxPro 6.0 Professional Edition

8/16/1998

9/30/2003

9/30/2004

Visual FoxPro 7.0 Professional Edition

9/29/2001

10/10/2006

10/14/2008

Visual FoxPro 8.0 Professional Edition

3/12/2003

4/8/2008

4/9/2013

Visual FoxPro 9.0 Professional Edition

12/22/2004

1/12/2010

1/13/2015

 

And just for comparison, here are a couple of other tools…

Visual Studio .NET 2003 Professional Edition

   7/10/2003

   10/14/2008

   10/8/2013

Visual Basic .NET 2003 Standard Edition

   7/10/2003

   10/14/2008

   10/8/2013

Visual Studio 2005 Team Suite

   1/27/2006

   4/12/2011

   4/12/2016

Visual C++ 2005 Express Edition

   1/14/2006

   4/12/2011

    4/12/2016

As you can see, the pattern is standard. 5 years on "Mainstream" support plus a further 5 years on "Extended" support. For definitions of exactly what this means, see the details on the FAQ page here: http://support.microsoft.com/gp/lifepolicy

Basically once the mainstream support phase ends, Microsoft will no longer provide non-security related hotfixes, provide no-charge incident support or entertain Warranty claims or Feature/Enhancement requests.

So what does that mean for you and I? Probably not much! It is pretty rare for the average developer to use "incident support" anyway, and for a mature and stable product like VFP it is even rarer.

But there won't be another version!

So what? What difference does that really make. Ask yourself the following questions:

Is VFP going to stop working on 1/13/2015 ?

(Hint: NO)

Is VFP's data handling and integration capability going to go away or be reduced?

(Hint: NO)

Will the features that make VFP your development tool of choice change?

(Hint: NO)

Will you not be able create and distribute royalty free EXE data-based applications?

(Hint: NO)

Will the capabilities and extensibility that make VFP the most powerful desktop database change?

(Hint: NO)

Which is the most widely used version of Visual FoxPro in the world today?

(Hint: VFP 6.0! A product whose support expired almost 3 years ago…)

In short, what will change? Answer, nothing at all! (Except, of course, for the vindication of the perception of the uninformed, marketing and buzz-word driven, benighted people who wouldn't dream of considering or using VFP anyway).

So what I am I doing about it all? Nothing! My clients typically are small to medium size businesses who are looking for fast, cost-effective (i.e. cheap!) solutions to business problems. Mostly they require solutions that run on desk-top PCs over secure Local Area networks. Typically they couldn't care less what the solution is created with. What they require is results, not conformity to corporate strategic plans.

Of course I will continue to provide the best service I can for my clients; where that means using SQL Server for a database, I will. If it means developing a browser based application in ASP .NET and C# then I will do that (and indeed, I have done so). But when I need a middle tier, data aware COM component, or a simple desktop application that accesses only data held locally then I will still be turning to Visual FoxPro as my number one development tool.

posted by andykr | 1 Comments

Are on-line technical forums breeding lazy developers?

As most of my readers will know, I spend a considerable amount of time trying to help other developers with technical issues in various on-line forums. I don’t do this because I am seeking some sort of kudos, or recognition, but because when I was starting out as developer back in the late 1980s I received a lot of help from other people. Most of them I had never heard of (but I have been very fortunate in that many of these ‘names’ I now count among my friends) but they were unfailingly helpful and always ready to offer advice, code and the benefit of their own experiences. Now I try to repay them by helping others who are just starting out, or who have hit a block of some sort.

However, I have noticed a rather, disturbing (at least to me), trend recently. It seems that more and more developers are using the on-line forums as the FIRST place they go for solutions rather than, as has always been the case, the last resort – when you have exhausted all other resources. Here is a question I saw posted today (yes, this is real, and un-edited):

I would like to know what is wrong with the following statements:
DIMENSION cust[2]
cust[1] = "aaa"
cust[2] = "bbb"
CREATE CURSOR CUSTOMERS FROM ARRAY cust
I get the error "No fields found to process".

I simply cannot imagine why anyone would post a question like this because the answer is immediately obvious from the help file topic for CREATE CURSOR where is states explicitly that:

Wouldn't it be reasonable to expect that a developer, on getting an error like "No fields Found to Process" would jump into the Help File to check that the code is correct? If so, even a cursory check for syntax would have revealed that the array was just plain wrong! A moment’s thought, and a quick search for “From Array” would have revealed a number of options including "APPEND FROM ARRAY" which obviously will do exactly what was wanted, but also "REPLACE FROM ARRAY" and "COPY TO ARRAY” which might have repaid a little further investigation and would have solved the problem in about 30 seconds.

Instead, the person posted a question, received an answer in 15 minutes, re-posted the “correct” question which was “how can I append data from an array to a cursor” and (presumably) waited another quarter hour for an answer to that one!

Or how about this one:

How to use ReportListener with Top-Level form and what Enginerbehaviour and Reportbehaviour shuould set. Please Help

What sort of question is that? This is on a par with “How do I write an application in VFP?” or “How long is a piece of string?

Maybe it’s me, but I get the feeling that people are getting lazy and expect, nay, even DEMAND, that others spoon-feed them answers to questions that really the questioner could have answered themselves with minimal effort and research. I actually replied to the person who asked the ReportListener question as follows:

Pardon me for jumping in here, but I think you are being very unreasonable. The first thing you need to do is to make some effort to do your own research, you can't expect others to do it all for you!

Do you have the VFP Help file? If so, try looking in the help file under: "ReportListener Base Foundation Class". There is an example at the bottom of the page!

On the other hand, maybe just don't like the VFP Help files. So how about going to the VFP Website and looking at the "Technical Article" section? Maybe you could download Cathy Poutney's articles (which includes a load of examples):

What's New in the Visual FoxPro 9.0 Report Writer and Visual FoxPro 9.0 Report Writer In Action

There is also Doug Hennig's article on "Hyperlink Your Reports" and there are other useful links there too.

On the other hand, maybe you don't like the Microsoft Web site either. So how about going to www.google.com and entering "VFP Report Listener Examples" - would you like me to list all 12,200 hits on that one for you (or can you manage to look at those by yourself)?

If you don't like that either, what about this forum's archives? There must be dozens of threads here discussing ReportListeners and absolutely chock full of good advice.

Look, if you have a specific question, we are only too glad to help, but asking others to do your research work for you is unfair and unreasonable. Go and do some research and when you find something you can't understand, or can't cope with, come and ask by all means. But what you are doing now is just abusing the goodwill of the members of this forum.

 

And the last paragraph says it all – it seems to me that this is a growing trend among some developers who seem to believe that there is no point in doing any research, or investigation, because you can just post the question on-line and someone else will do it for you.

Now, I know that I don’t have to answer these sorts of questions, but the thing is that I really believe that the only way to become a better developer is to really understand what is going on and how your development tool works. But I don’t think you can get that sort of understanding without putting SOME effort of your own into acquiring it.

So the point of this article is two-fold.

[1] Am I being over-sensitive about this? Or are people really just getting lazier because it’s so easy to get answers on-line?

[2] How can we (and maybe even SHOULD we?) encourage people, especially neophytes, to appreciate the value of doing their own research before posting questions on line?

 

posted by andykr | 15 Comments

Design Patterns – The Façade

The façade is, perhaps, the second most commonly implemented design pattern. Just like the Bridge (probably the most widely used pattern of all, and certainly the most fundamental) it is usually implemented without a conscious decision having been made. Does this mean that it merely a statement of the obvious? Perhaps, but the pattern does have a very specific intent, and should definitely be taken seriously.

What is a Façade?

The formal definition for Façade offered by Erich Gamma, Richard Helm, Ralph Johnson and John Vlissides is:

Provide a unified interface to a set of interfaces in a subsystem. Facade defines a higher-level interface that makes the subsystem easier to use

Succinct, but perhaps not quite so obvious after all. Consider the basic three tier architecture so widely used in software design. How should you define the middle tier? Potentially there are going to be many different objects in that middle tier. For example you may have any, or all, of the following types of object:

·         Data Validation objects
·         Business Rule Objects
·         Data Transformation Objects
·         Data Access/Database Connection Objects

How is your Presentation Tier going to manage all the interactions with these individual objects? You could easily end up with a frightful mess where each object in the Presentation Tier must have detailed knowledge of each of the middle tier objects and has to handle them directly. The resulting system diagram looks horribly like that shown in Figure 1.

Not only is this aesthetically displeasing, it illustrates that we have a tightly coupled system which is, as always, prone to error when things change. Changes to any of the middle tier components must be reflected in all of the Presentation Tier components that access them!

Figure 1: Cross-Tier interactions can get messy!

By implementing a Façade the system becomes much simpler as shown in Figure 2.

 

Figure 2: Cross Tier Interactions with a Façade Object

Not only is this aesthetically more pleasing but, obviously this is a much more robust design.

Notice that the Presentation Tier objects no longer need detailed knowledge of the individual components of the middle tier. Instead they only need to know how to access the Façade object which is then responsible for controlling all further interactions within the middle tier. This achieves a weakly coupled state that, in turn, means that we can change things at will in the lower level sub-systems without needing to make corresponding changes elsewhere. Providing that the interface exposed by the façade object does not alter, then its internal facing methods are the only places that changes to lower level systems can ever affect.

When should we use a Facade?

The purpose of the façade is to provide a simple interface to a complex system. Although most of the examples in pattern literature talk about ‘sub-systems’ you must remember that a design pattern is not dependent upon the implementation but on the design problem which it addresses. So while it is may not be very common for us, as FoxPro developers to build “complex sub-systems”, we often create complex objects which expose many methods in their interface! So whenever we find ourselves having to call multiple methods on the same object we really should be thinking in terms of implementing a façade.

The usual scenario occurs when we have to run some multi-step process or calculation. Usually our first instinct is to add the relevant methods directly to the object (usually the form!) that requires them. When this gets messy we may provide a ‘control’ method to call the various steps in the correct order and to evaluate results and act accordingly. The resulting code looks something like this:

LPARAMETERS tuInval
llStatus = This.CheckParameters( tuInval )
IF llStatus
  luResult = This.FirstStep( tuInval )
  IF VARTYPE( luResult ) = “N” AND NOT EMPTY( luResult )
    llStatus = This.SecondStep( luResult )
  ELSE
    llStatus = .F.
  ENDIF
ENDIF
RETURN llStatus

This will work perfectly well, of course, until some other object requires access to the code. Now we have a problem. The next stage is to abstract the code from our form and create a stand-alone object, but now we have to make multiple calls to that object – and from more than one place in our application – sound familiar? It should, because this is precisely the scenario that the façade pattern addresses.

Our object should not actually expose all of its methods, instead it should expose a façade consisting of just a few very simple methods which then call other methods internally as needed. By designing things this way we achieve several things:

·         The code can be called from any object
·         The process detail is hidden, callers only need to know the exposed interface
·         Changes to the implementation are confined to the façade object

How can we implement a façade?

The example given here is for a converter class, defined in Visual FoxPro, that can be implemented directly as a VFP application object, or as a COM component. The interface for this object is extremely simple, and consists of just TWO methods:

·         Convert             Which expects three parameters, a value and the appropriate From/To codes. This method returns the converted value or an error code

·         GetErrors           Interrogates the object’s errors collection and returns either a VFP Error Object or a COM exception, depending on how the object was instantiated

In fact the object has a number of methods which are defined as “Protected” so that they do not even appear in the COM interface, and can only be called internally.

The first thing that this method does, is to pass the received parameters through a validation method to ensure that they are of the correct (expected) data type. We are not concerned, at this point, if the values are meaningful, just that they are expected. Since this method is the only entry point, once we have checked the parameters, we can assume that they are correct in all other places because the only way that values can be sent to another method is from this, or a subordinate, method. This is an additional benefit of the façade!

  *** First thing to do is to ensure we have From/To parameters
  IF NOT .ValidateParam( tuConvFm, "C" ) OR EMPTY( tuConvTo )
    lcErrText = "Invalid 'From' Parameter" + CRLF
  ENDIF
  IF NOT .ValidateParam( tuConvTo, "C" ) OR EMPTY( tuConvTo )
    lcErrText = lcErrText + "Invalid 'To' Parameter" + CRLF 
  ENDIF
  *** And that we have a value too
  IF EMPTY( tuInpVal )
    lcErrText = lcErrText + "No input value passed" + CRLF
  ENDIF
  *** Check the results so far
  IF NOT EMPTY( lcErrText )
    ERROR lcErrText
  ENDIF

Next we need to try and figure out what method we need to call. This is the second direct method call, and the method called returns the name of the conversion method we want, based on the ‘from’ parameter. One of the “rules” I have implemented is that both “From” and “To” measures must be of the same type (you cannot convert a temperature to a length for example).

  *** Now we need to decide what method to call, using the From Parameter
  lcMethod = .GetConv( tuConvFm )

Finally we execute the relevant conversion and get the result

  *** Now just handle the conversion
  luRetVal = EVALUATE( "This." + lcMethod + "( tuInpVal, tuConvFm, tuConvTo )")

This entire block of code is enclosed in a TRY… CATCH so that any error, either here, or in a subordinate method, is caught and sent to the LogError() method for access when needed. The return value is either the converted value, or NULL and it is, of course, up to the client object to determine what to do next.

Façade Pattern Summary

As you can see the façade offers us a lot of benefits as developers by providing us with a methodology that avoids tight coupling and, at the same time, promotes better encapsulation and isolation of code so that the impact of changes are minimized.

I illustrated the façade pattern by using an object dropped on to a form and named explicitly at design time and the façade object also contained the actual code to be implemented. However, there is no absolute reason to do it this way. The façade object could simply have been a “gatekeeper” to a host of other objects and, using an implementation of the Strategy pattern, could instantiate the required object for a task at run time. Remember, patterns can be (and usually are) combined in order to solve real design problems.

posted by andykr | 0 Comments

Design Patterns - The Mediator

What is a mediator and how do I use it?

The mediator describes one solution to a key issue that we all encounter whenever we try to design a new class. How to deal with situations where one object has to respond to changes in, or control the behavior of, another.

How do I recognize where I need a mediator?

The formal definition of the mediator, as given by the “Design Patterns, Elements of Reusable Object-Oriented Software” by Erich Gamma, Richard Helm, Ralph Johnson and John Vlissides is:

Define an object that encapsulates how a set of objects interact. Mediator promotes loose coupling by keeping objects from referring to each other explicitly, and it lets you vary their interaction independently.

This addresses one of the most fundamental problems that we have to tackle in any software development task, that of ensuring that objects can communicate with each other without actually having to include hard-coded references in our classes.

Nowhere is this more critical than when building the complex user interfaces that the current generation of computer-literate end users not only expect, but demand. Typically we have to make the entire UI respond as a single entity, enabling and disabling functions and controls in response to the user’s actions and choices, or according to their rights and permissions. At the same time we want to design and build generic, reusable classes. The two requirements are, apparently, in direct conflict with one another.

Consider the problem of kinking a couple of textboxes in a form. When a value is entered into one, the other must display the result of some sort of calculation based on that value (e.g. When entering a price in one, display the Sales Tax in the other). Obviously, if there is no value in the source, there should be no result in the target, but how can we ensure that the calculation only gets done when a value greater than 0 is entered?

Of course, the simplest solution is just to add a couple of lines of code to the Valid() of the textbox that would call the necessary functionality like this:

IF This.Value > 0
  *** Calculate the tax display
  ThisForm.TxtTax.Value = ( This.Value * (5.75/100) )
ELSE
  *** Clear the tax display
  ThisForm.TxtTax.Value = 0
ENDIF

This sort of ‘tight coupling’ may be acceptable when we are dealing with just a simple pair of controls in a single form. However, we will quickly run into trouble if we try to adopt this solution when dealing with multiple controls that have to interact in complex and differing combinations. Even finding whe