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