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 forms Open event and
clean up in the forms 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 frameworks init function, and a call to the zip
class term in your frameworks 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 doesnt 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, Im
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 doesnt
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 frameworks 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 wont 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