[AccessD] Framework Discussion - Service Classes

John W. Colby jwcolby at colbyconsulting.com
Fri Mar 19 19:08:08 CST 2004


Framework Discussion – Service Classes
It is time to discuss the framework foundation and some programming concepts
that we need to be using to make our projects as stable as possible.  We
will be using many service classes, classes which provide a service to the
framework and even to the Application itself – services such as zip / unzip,
encrypt / un-encrypt, Sysvars and the like.  There are a couple of ways that
we can implement this functionality.  We will discuss these variations, why
we would use one or the other and then I will select the one I like to use
(a framework class) and begin building that.

Service classes on demand
Service classes are “standalone” classes which just do something for the
developer, provide some packaged functionality.  The application might need
to zip files to attach to email messages, so the developer could have a
class that he instantiates at the moment he wants to use the zip service,
then clean up after himself when he is done.  The developer could dimension
the variable in a form header, set the instance in the form’s Open event and
clean up in the form’s Close event.

Option Compare Database
Option Explicit

'Create a variable to hold the zip class
Dim fclsZip As dclsZip

Private Sub Form_Close()
    'cleanup the pointer to the class
    Set fclsZip = Nothing
End Sub

Private Sub Form_Open(Cancel As Integer)
    'setup a pointer to the class
    Set fclsZip = New dclsZip
End Sub

Private Sub cmdZipTheFile_Click()
    fclsZip.AddFileSpec CurrentProject.Path & "\" & "1. Classes -
Collections classes and Garbage collection.doc"
    fclsZip.Zip
End Sub


Or he could dim a class variable inside of a function and set / cleanup all
right inside that function.

Private Sub ZipIt()
Dim fclsZip As dclsZip
    'setup a pointer to the class
    Set fclsZip = New dclsZip

    'do the zip
    fclsZip.AddFileSpec CurrentProject.Path & "\" & "1. Classes -
Collections classes and Garbage collection.doc"
    fclsZip.Zip

    'cleanup the pointer to the class
    Set fclsZip = Nothing
End Sub

These are probably the most common ways of using classes, and the ways that
pop to mind for the majority of developers.  There is certainly nothing
wrong with doing things this way, however the developer has to dimension
variables and set a class instance and remember to clean up afterwards.
Service Classes as Services – Old Style
The “framework” way of doing this is to set up the service globally, decide
whether you need the service for this application, then if the app will need
it, have the framework load the class once as the framework loads, and the
framework cleans up the class as the framework unloads.

Option Compare Database
Option Explicit

Private mclsZip As dclsZip
Private mblnZipInitialized As Boolean

Public Function ZipInit()
    If mblnZipInitialized = False Then
        'setup a pointer to the class
        Set mclsZip = New dclsZip
        mblnZipInitialized = True
    End If
End Function

Public Function ZipTerm()
    'cleanup the pointer to the class
    Set mclsZip = Nothing
End Function

Public Function cZip() As dclsZip
    ZipInit
    Set cZip = mclsZip
End Function

Your app can if desired just call the cZip function which initializes the
zip class if it is not initialized, then returns a pointer to the class
which you use to zip your file.  Generally though you would place a call to
the zip class’ init in your framework’s init function, and a call to the zip
class’ term in your framework’s term().

Private mblnFWInitialized As Boolean

Function FWInit()
    If mblnFWInitialized = False Then '
        mblnFWInitialized = True
        ZipInit
    End If
End Function

Function FWTerm()
    ZipTerm
End FunctionPrivate mblnFWInitialized As Boolean

This lets the framework worry about what is required to set it up and what
is required to clean it up.  The zip class is just a service and is
available if you need it.  Of course it is inefficient to load services that
the app simply doesn’t need.  Once we have covered the SysVar class I will
show you how to turn on/off services using SysVars, and even how to override
the default behaviors inside of the application to turn on a service that is
off by default (as Zip would normally be).

So this method basically sets up a global (but private) pointer to a service
class, then provides functions to init(), Term() and return a pointer to the
class.

This is one step up from the first method we described where the developer
does it all right in the form / function where the class is used.  This
method provides the advantage of having the class initialized and terminated
automatically which is a good thing, the developer no longer has to worry
about that stuff, and as we know forgetting to terminate classes can have
serious consequences so the fact that the framework cleans it up for us is a
major reason to use this method.

The downside is that generally, once loaded it stays loaded.  This may or
may not actually be a downside, occasionally a service class has to do some
time consuming setup task and once loaded it is just better to leave it
loaded.  Again, it is up to the developer to decide which is right for him,
but my feeling is that in modern computers if my application needs to zip a
file, the memory overhead of leaving a zip class loaded is acceptable, I’m
not going to worry too much about the additional few Kbytes of memory.  In
many cases it is possible to set up service classes that you can load and
unload at will, while still allowing the framework supervisor to do the
init() / term(), so if you are the type that is going to lose sleep over
leaving the class loaded, look at doing that.

Service Classes as Services – Framework Foundation Style
My preference is to use a class that is the foundation of the framework,
which I will call clsFramework.  ClsFramework is then initialized in an Init
module and a pointer to that class is passed back to the caller.  The
service classes all have variables dimensioned in the header of clsFramework
and clsFramework then has methods that pass pointers to the various service
classes back to the caller.  The module that initializes and terminates the
framework looks like:

Option Compare Database
Option Explicit

Private mcnn As ADODB.Connection        'currentproject connection
Private mfwcnn As ADODB.Connection      'A connection for the code project
Private mclsFramework As clsFramework   'The framework foundation class
Private mblnFWInitialized As Boolean    'A boolean to tell us that we have
already initialized

Function FWInit()
    If mblnFWInitialized = False Then '
        mblnFWInitialized = True
        Set mfwcnn = CodeProject.Connection
        Set mcnn = CurrentProject.Connection
        Randomize
        Set mclsFramework = New clsFramework
        mclsFramework.Init Nothing
    End If
End Function

Function FWTerm()
    mclsFramework.Term
    Set mclsFramework = Nothing
End Function

Function FW() As clsFramework
    Set FW = mclsFramework
End Function

Function gcnn() As ADODB.Connection
    Set gcnn = mcnn
End Function

Function gfwcnn() As ADODB.Connection
    Set gfwcnn = mfwcnn
End Function

A couple of notes:

One programming “best practices” says that we should make our global
variables for things like the framework class “read only” by dimming them
private in a module.  We then perform our initialization and create a
function that returns a pointer to that “read only” variable.  This allows
the developer to use the framework (or other variable) freely but doesn’t
allow them to unintentionally (or intentionally) set that variable to
nothing, inadvertently crashing the system.

Thus I have a private framework variable, an Init() that gets called to
initialize the framework, a term() that is called to shut down the
framework, and a function that returns a pointer to the framework class
instance.

Notice I also have “global” pointer functions to the application and
framework project (Access FEs) connections for ADO.  We need both since we
will eventually be referencing data in the framework (Sysvars) as well as in
the Application.

Now that we have a framework class, when we need service classes we just put
private variables for those classes in the header of that class and allow
the init of the framework class to initialize the service classes.

clsFramework is just our template class as the base, with stuff added so we
have already seen all of the generic “framework” stuff that comes with any
class in our system.  We dim a couple of variables to handle the zip class.

'*+ custom variables declarations
'Service class for Zip
Private mclsZip As dclsZip
Private mblnZipInitialized As Boolean
'*- custom variables declarations

As I have mentioned before, we use the class’ Initialize event to set all of
our class and collection pointers to new instances of that object.  This
will change once we get a SysVars class functioning but for now:

Private Sub Class_Initialize()
    Set mclsZip = New dclsZip
End Sub

In the Init() of the framework class we initialize the zip class:

Public Sub Init(ByRef robjParent As Object)
    mclsZip.Init Nothing
End Sub

And in the term of the framework class we cleanup the zip class:

Public Sub Term()
    mclsZip.Term
    Set mclsZip = Nothing
End Sub

All of which allows the framework to do the work for us of setting up and
cleaning up the various service classes.

We can if we wish set up a private method of the framework class to
initialize the zip class:

'*+PRIVATE Class function / sub declaration
Private Function ZipInit()
    If mblnZipInitialized = False Then
        'setup a pointer to the class
        Set mclsZip = New dclsZip
        mblnZipInitialized = True
    End If
End Function
'*-PRIVATE Class function / sub declaration

This allows us to not initialize the class in the framework’s Init() or
Initialize and simply do so when and if the service class is ever used.

We then build a method in the framework class to expose the pointer to the
service class:

'*+PUBLIC Class function / sub declaration
Public Function cZip() As dclsZip
    ZipInit
    Set cZip = mclsZip
End Function
'*-PUBLIC Class function / sub declaration

This has a tiny overhead of calling the init function for the Zip class
every time we try to get a pointer to the class, but that may be acceptable
to avoid having the class always loaded.

So now, in your code to zip a file you simply call the function that gets a
pointer to the framework:

	Fw

Add a reference to the zip method of the framework class, which of course
gets a pointer to the zip class:

Fw.cZip

Then call the various properties / methods of that class:

Fw.cZip.ZipFile = CurrentProject.Path & "\" & "TextZipDemoCtlClassV2.Zip"

Summary
My preference when building is to build a foundation class which is tasked
with setting up and tearing down all of the various classes that will be
used by the framework itself or the application as services.  One of the
advantages of this is that you no longer have to worry about exposing all of
the various classes in the framework library, plus you get the peace of mind
of knowing that the framework foundation class will handle all of the work
of setting up and tearing down the classes automatically.  If we develop the
framework correctly, once it is in use in the application we won’t be
forgetting to terminate a class somewhere and hanging Access or even
Windows.

Class programming can be made easier by using a framework to handle all of
the setup and teardown processes for us.  Of course we still have to get it
all set up and working as a framework, but once in use out in your
application it all just runs like a well oiled machine, always, every time.



John W. Colby
www.ColbyConsulting.com





More information about the AccessD mailing list