Full Version: Classes
UtterAccess Discussion Forums > Microsoft® Access > Access Q and A
mrpersonality
i am trying to understand classes-viewed all the examples here and also by searching web,but cant get my head around what a class offers above a std module
i need to keep it simple and also need to be a subject i can relate to
heres my class


Option Compare Database
Option Explicit
Dim DogsName As String
Dim DogsAge As Long
Dim WhelpingDate As String
___________________________________________
Public Property Get Name() As String
Name = DogsName
End Property
_____________________________________________
Public Property Let Name(ByVal NewName As String)
DogsName = NewName
End Property
_____________________________________________

Public Static Property Get Age() As Long
Age = DogsAge
End Property
____________________________________________
Public Static Property Let Age(ByVal Months As Long)
DogsAge = Months
End Property
_______________________________________________
Public Property Get Whelped() As Variant
Whelped = WhelpingDate
End Property

_____________________________________________
Public Property Let Whelped(ByVal Newdate As Variant)
WhelpingDate = Newdate
End Property
_________________________________________________
Public Function GetDogProperty(DogId As Variant, WhatToReturn As Long) As Variant

Dim Qstring As String
Qstring = "SELECT ListOfGreyhounds.DogId, ListOfGreyhounds.OwnerId, ListOfGreyhounds.TrainerId, ListOfGreyhounds.DogName," _
& " ListOfGreyhounds.Colour, ListOfGreyhounds.Sex, ListOfGreyhounds.Whelped," _
& " ListOfGreyhounds.Sire, ListOfGreyhounds.Dam, ListOfGreyhounds.PrizeMoney" _
& " FROM ListOfGreyhounds" _
& " WHERE (((ListOfGreyhounds.DogId)=" & DogId & "));"

With CurrentDb.OpenRecordset(Qstring, 2)
If Not .BOF And Not .EOF Then

Select Case WhatToReturn


Case 1
GetDogProperty = ![DogName]
Case 2

GetDogProperty = DateDiff("m", Nz(![Whelped], Date), Date)
Case 3
GetDogProperty = ![Whelped]
Case 4

Case Else

End Select

Else

'GetDogProperty = MsgBox("No property Found")
End If
.Close
End With
End Function


###################################################################
###################################################################

here is the code behind the click event on a list box
Dim Mydog As New BjDogs

With Mydog
.Age = .GetDogProperty(Me.DogList, 2)
.Name = .GetDogProperty(Me.DogList, 1)
.Whelped = .GetDogProperty(Me.DogList, 3)
MsgBox .Name
MsgBox "Aged " & .Age & " Months"
MsgBox "Date Whelped = " & .Whelped
End With

while all that works and returns the correct values,what advantage does that have over simply calling the function
GetDogProperty from a std module
i just dont get it
i really want to learn this class programming,so any help would be appreciated
i realise this may be a long process LOL
NevilleT
OK. This is the simple, not quite technically correct explanation.

Think of a form as a class. It has properties (width, height, etc). You can do things with it as it has events (on open, on change, on getfocus). You can use it over and over again. Instead of creating everything from scratch you just call an instance of the form. Now your dog class is similar. You create it once and can use it over and over again. It has properties such as name. You could also create events for it if you wanted. For example, you could display an image of the dog.

I use a class to store values. It has a series of properties called Arg1, Arg2, Arg3 etc. Within a form I create an instance of the class, and assign values to the arguments. I can then close the form and the values are still there and accessible from another form or report through code. I used it in one program to store security information which was encrypted. I had to unencrypt the information using an algorithm. The information was then stored in memory in a class. When a person tried to open a form, the form checked the class to see if it was valid to open the form for this user. Saved having unencrypted information in a table which could be viewed.

Hope that makes a bit more sense.
mrpersonality
Not really
the database i have is used as a form guide for racing greyhounds.in its present form it works,but i want to see if using classes is a better way to go
i can see you can set as many propertys as you like in the class.What i cant understand how you use them
in my example i have to get the various value from the table to set the property values-whats the advantage ?
instead of setting then getting the values from the class,why not just get them from the function
i am sure once the penny drops i will grasp the concept and use it to its fullest-i just dont get it LOL
i guess i cant grasp how to use the dog class to my advantage-it seems more code for same function return value
AlbertKallal
A great question, and the answer is there is little if any advantage to pull some values from a table and shove them into an object and THEN use that object to manipulate that data.

I mean if you have a table and you need to retrieve a few values from that table then the idea that you should build a class object, write all that extra code, and set up a bunch of properties to what amounts to adding a whole NEW layer of software on top of a handy dandy record set makes absolutely no sense here at all.

However that doesn't mean assembling and building class objects is not a great practice to adopt but ONLY do so WHEN it makes sense. I mean, this is quite much like asking when it is a great idea in your software to use a subroutine as opposed to leaving the code where it is? Because I'm writing some code, and I wanna learn about subroutines, I'm not going to start running around and start moving bits and parts of code out of my existing system that don't benefit from using a subroutine.

The same thinking and reasoning here needs to be applied to class objects. You don't just start runnng around and trying to use one because someone's telling you how great they are!.

You NEED a reason to use class objects. And just like a subroutine is great WHEN they make sense, the same goes for class objects. When you have a GOOD reason to use them, then the efforts of doing so will yield you the benefits.

However, it think it needs pointing out that a standard record set is a terrific example of when and why one SHOULD use a class object. Of course the record set is a BUILT IN object and not a custom class object. However just think of a record as a great example of a class object. If you think about how a record set operates and functions, when you have a task in your application that requires some data from a table, and you need to loop on, and use code (methods to operate on that data), then you have use case here.

In other words to add some data from a table I don't need to use a reocrdset, and most of the time I can just build a form and not even write some code here. So in this case it makes little sense to even use a record set object and start writing code when the form can do all this work for me. So in this case it makes little sense to create a record set does it? And it mkaes even worse sense to build a custom class object.

So as a example type of object, a record set is great. That reocrdset has all kinds a great properties like the number of rows, field names, and a whole grouping of information that you need to achieve your task at hand. Now in place of having a record set system in Access, they could have forced you as a developer to declare a whole bunch of global variables. I mean if the object did not have all those properties and mehods, then you would have to use varibles to store all those values like table name, the number of rows. And you could call subroutines for commands like findfirst, but now how will findfirst routines get all of the field names? So while all of these things as you say can be done by standard code and subroutines. However the MAJOR concept here is the grouping of all those methods that operate on that data in question. So, you can have 2, 3 or even more reordsets open, and each one probably would represent about 15 or more varibles you would have to delcare as a group. So the "grouping" concept is important here (especially when you need more then ONE of the "thing" in question.

If you only need "one" of the "thing" in question, then again you will see little need for a class object.

I should also point out that if your application was simple, then all those features of a record set may never even be used and this would especially be the case if you don't have to write any code to manipulate that data, and using basic forms was sufficient .

I'm sure when you first started using access, for the most part you were NOT writing record set code, and for the most part you might even wondered what a record set is for?

So taking a bunch of values from a table, and shoving and into an object, then using that code to get at the data normally doesn't help. On the other hand, I bet you would frequently do this more often if we didn't have the great wonderful record set built in object.

I have an article here that explains the reasoning and thinking process as to why and when it makes sense to use a class object.

http://www.kallal.ca/Articles/WhyClass.html

The main motivation to start using class objects is when your code and you the developer are having difficulty to manage the group of related information and data with variables and code.

So that "group" of code and that "group" of data is key here.

In other words, class objects tend to allow you to group together a group of related functions and features, and allows those functions and features operate on a chunk of data. For simpler applications and code, you will not find much use for class objects and there's little advantage to do so. The above article should however help anyswer this question as to why and when.

Albert D. Kallal (Access MVP)
Edmonton, Alberta Canada
kallal@msn.com
mrpersonality
Wow-thanks albert
its usually about here where it all gets to hard and i just keep doing whats been working for me
However,not this time,i am determined to come to grips with this class business as i want to improve two things,my knowledge and my program
your article describes exactly what i do LOL
and i would say a class would definately help
i need to collate lots of different data about greyhound races (theres a max of 8 starters in a race,usually 11 or so races per meeting,and i cover 7 or so meetings a week)
to do this i use tables to store race fields,race results,sires,dams,trainers,owners,dogs,some of the crucial habits certain dogs posses etc
i have coded routines to download from the net all of the raw data,sort it and store it in the tables,so data entry really only occurs when i view and evaluate a completed race
i use forms and subforms to display race fields ,command buttons to apply lots of different filters to try to make viewing the data easier
the more i use my program,the more i can see other logical factors that influence the decision of which greyhound has the most going for it in a race,and whether a wager might be in my favour
so it would seem a class would be the place to calculate the complex question of what does a particular greyhound have in its favour(or not)
some things that can effect it are
does it possess
1.a favourable box
2.the speed to take advantage of that box
3.is there other dogs on its inside or outside that may make it harder or easier for this dog
4.does it like this race track(of course based on its history at that track)
5.is it in a favourable grade (grades determine the ability of the dogs-at least they are supposed to)
6.does it perform better in daylight conditions or night racing
7.does it perform better when drawn to race in an early race(there are usually 12 races per metting,and some dogs wear themselves out barking all night if they are in say the last few races)
8.what sort of early speed does it have
9.what direction of the track does it head for once the race starts
10.is it clumsy when crowded by other dogs
11.does it give up easliy when pressured
the list goes on-so you can see its not a simple query to ask for each starter in a race
all the above questions need to draw information from the data tables,and its getting harder to maintain and slower

_________________________________________________________
from your example
"Now, if you look at the above property of the object, you will notice that a recordset m_rstBusList is already loaded"
how is that loaded -how does the class hold all the different recordsets required to answer the queries asked of it




AlbertKallal
QUOTE (mrpersonality @ Apr 8 2012, 04:06 PM) *
from your example
"Now, if you look at the above property of the object, you will notice that a recordset m_rstBusList is already loaded"
how is that loaded -how does the class hold all the different recordsets required to answer the queries asked of it


Excellent question. The answer is all the values that you want to be "in state" or part of the class and COMMON to all routines are simply declared at the start of the code module (so just like in a stardard code module any vars you create become global and "in scope" to all routiens in that module.

So you just declare variables before any code functions etc. And note that you can those vars as public. This means you then don't need public functions (or property gets) to expose values. This saves lots of coding and makes sense when you want to use variables directly (no need for declare separate properity get/let).

In fact, you often have many values that you need exposed and such vars are often called "members" of the object, and many developer precedie these vars with a m_.

I cannot post all of the class object (it too long, and there is properitary code I don't want to share). However one simple declares all of the variables that are common to all functions are decleared at the start like in a regular code module. And as noted any public vars will even appear in intel-sense when you write code.


Here is what part of the start of that code module looks like:

CODE
Option Compare Database
Option Explicit

'*******************************************
' Object Class CRide                       *
' Main Reservation Booking Class Object    *
' C - Albert D. Kallal                     *
' Jan  2001                                *
' ******************************************

Public m_TourId         As Long              ' Key id of tour
Public m_rstTour        As Recordset         ' Tour Record
Public m_rstTourRates   As Recordset         ' Tour rates
Public m_rstHotel       As Recordset         ' Hotel record
Private m_rstRoomCount  As Recordset

Public m_rstBusList     As Recordset


Private m_rstGroupRooms As Recordset
Public m_rstTourOptions As Recordset


' Exposed Class object properties (values)
Public m_GstRate As Currency                 ' Gst Rate (based on tour start Date)
Public m_PstRate As Currency                 ' Pst rate (based on tour end date)


Public m_HotelRooms        As Integer  ' total number of rooms
Public m_HotelSpaceRemain  As Integer  ' people capacity of rooms remain
Public m_HotelSpace        As Integer         ' capacity of rooms
Public m_HotelRoomsUsed    As Integer  ' number of rooms used


There is more, but you can see at least 8 or more recordsets here.

So, to load up everything about a tour (hotels, rates, bus, bus company), I simply do this:

clsMyTour.TourID = me.TourID

And, again while not appropriate to post huge amounts of code, the first part of this code TourID setting looks like this:

CODE
Public Property Let TourId(LoadID As Long)

   ' Loads/setsup the tour object (pricing etc..)
   ' DO NOT load again if id is same as passed.
  
   Dim myDB             As Database
   Dim qryBusList       As QueryDef
  
   Set myDB = DBEngine(0)(0)
  
   If m_TourId <> 0 Then
      ' tour is loaded, check for same tour...
      If m_TourId = LoadID Then
      
         ' tour already loaded...don't re-load
         Exit Property
        
      End If
   End If
  
   m_TourId = LoadID
  
  ' loads the load rates....

   Set m_rstTour = myDB.OpenRecordset("select * from tbltours where id = " & m_TourId)
  
   With m_rstTour
      If .RecordCount > 0 Then
        
        m_FromDate = !FromDate
        m_Todate = !ToDate
        m_HotelId = !Hotel
        m_dtDepositDue = m_FromDate - !DaysDue
        
        Set m_rstHotel = myDB.OpenRecordset("select * from tblHotels where id = " & m_rstTour!Hotel)
        
        Set qryBusList = myDB.QueryDefs("qryTourBusListB")
        qryBusList.Parameters(0) = m_TourId
        Set m_rstBusList = qryBusList.OpenRecordset


So by simply setting the TourID = to PK of tblTours, then I just load up the record sets and what I need. I suppose some code could be changed to ONLY load the reocrdset when that particlar value is required, but the setup in most cases does require those recordsets to be loaded.

Now ALL of those values and those several tables and recordsets are now available to ALL of the code (methods) of that object.

So, to get the hotel name, we have this code:

CODE
Public Property Get HotelName() As String

   HotelName = m_rstHotel!HotelName
  
End Property

And note that I have public exposed the recordsets, so I can even go:

clsRides.m_rstHotel!HotelCity

So, I have properties for many things, but in some cases, I will just use the recordset directly or those member vars directly (so no need to create those property let/gets).

Some of the properties are outlined here:

http://www.kallal.ca/Articles/PickSql/Appendex4.html

And that above is from this article where I talk about class objects:

http://www.kallal.ca/Articles/fog0000000003.html

The above is an interesting read and I share many ideas on using class objects.


Albert D. Kallal (Access MVP)
Edmonton, Alberta Canada
kallal@msn.com
mrpersonality
Thanks Albert-i think i am starting to get somewhere
unfortunatley posting a lot of code is my only chance of grasping the concept
heres what i have so far-its basic because i am experimenting with things,so some of what i have done returns only basic stuff,but its getting me used to how i may use a class
it appears to cut down a lot on code when you start to do more complex things
Option Compare Database

Option Explicit
'###### table data
Public m_rstDogName As DAO.Recordset
Public m_rstTotalStarts As DAO.Recordset
Public m_rstTotalStartsThisTrack As DAO.Recordset
Public m_rstRaceLineup As DAO.Recordset

'#### variables
Public m_StarterId As Long
Public m_TrackId As Long
Public m_RaceId As Long


________________________________________________

Public Property Let StarterId(LoadID As Long)

' Loads/sets up the starter object
' DO NOT load again if id is same as passed.

Dim myDB As Database

Set myDB = DBEngine(0)(0)

If m_StarterId <> 0 Then
' a dog is loaded, check for same dog...
If m_StarterId = LoadID Then

' dog already loaded...don't re-load
Exit Property

End If
End If

m_StarterId = LoadID

' loads the relevant recordsets....
Set m_rstDogName = CurrentDb.OpenRecordset(" SELECT ListOfGreyhounds.DogName" _
& " FROM ListOfGreyhounds" _
& " WHERE (((ListOfGreyhounds.DogId)=" & m_StarterId & "));")


Set m_rstTotalStarts = CurrentDb.OpenRecordset("SELECT ImportedRaceFields.DogId" _
& " FROM ImportedRaceFields" _
& " WHERE (((ImportedRaceFields.DogId)=" & m_StarterId & "));")


Set m_rstTotalStartsThisTrack = CurrentDb.OpenRecordset("SELECT ImportedRaceFields.DogId" _
& " FROM ListOfRaces INNER JOIN ImportedRaceFields ON ListOfRaces.RaceId = ImportedRaceFields.RaceId" _
& " WHERE (((ImportedRaceFields.DogId)=" & m_StarterId & ")" _
& " AND ((ListOfRaces.TrackId)=" & m_TrackId & "));")




Set m_rstRaceLineup = CurrentDb.OpenRecordset("SELECT ImportedRaceFields.DogId" _
& " FROM ListOfRaces INNER JOIN (ListOfGreyhounds INNER JOIN ImportedRaceFields ON ListOfGreyhounds.DogId = ImportedRaceFields.DogId) ON ListOfRaces.RaceId = ImportedRaceFields.RaceId" _
& " WHERE (((ImportedRaceFields.RaceId) = " & m_RaceId & "))" _
& " ORDER BY ImportedRaceFields.Box;")



End Property
________________________________________________
Public Property Get Name() As Variant

If m_rstDogName.RecordCount > 0 Then
Name = m_rstDogName!DogName
Else
Name = "No Record Found"
End If
End Property
________________________________________________
Public Property Get TotalStarts() As Integer
If m_rstTotalStarts.RecordCount > 0 Then

m_rstTotalStarts.MoveFirst
m_rstTotalStarts.MoveLast
TotalStarts = m_rstTotalStarts.RecordCount
Else
TotalStarts = 0

End If

End Property
________________________________________________
Public Property Get TotalStartsThisTrack() As Integer
If m_rstTotalStartsThisTrack.RecordCount > 0 Then

m_rstTotalStartsThisTrack.MoveFirst
m_rstTotalStartsThisTrack.MoveLast
TotalStartsThisTrack = m_rstTotalStartsThisTrack.RecordCount
Else
TotalStartsThisTrack = 0

End If

End Property
________________________________________________
Public Property Get StartRatio() As Double
StartRatio = Me.TotalStartsThisTrack / Me.TotalStarts
End Property
________________________________________________
Public Property Get MostExperienced() As String
Dim Rst As DAO.Recordset
Dim x As Long
x = 0

Set Rst = Me.m_rstRaceLineup

With Rst
If Not .EOF And Not .BOF Then
.MoveFirst
Do Until .EOF
Me.StarterId = ![DogId]
If Me.TotalStarts > x Then
x = Me.TotalStarts
MostExperienced = Me.Name & "," & x

End If
.MoveNext
Loop

Else
MostExperienced = ""
End If
End With

End Property
________________________________________________

on a form i have a list box that fills with race fields-i tried to get the most experienced runner in the race
basic-but it gets me thinking lol
Private Sub List7_Click()
Dim This_Starter As New cStarters
Dim myarray
m_TrackId=1 'this didnt work
m_RaceId = 8078 'this didnt work

With This_Starter
.m_TrackId = 1 'this will eventually be a variable
.m_RaceId = 8078 'this will eventually be a variable
.StarterId = Me.List7 'just used this as a visual to check the result was correct

Me.Text1 = .Name
Me.Text2 = .TotalStarts
Me.Text3 = .TotalStartsThisTrack
myarray = Split(.MostExperienced, ",")
MsgBox "most experienced runner is " & myarray(0) & " With " & myarray(1) & " Starts" 'not necessarily the list box value that was clicked

End With

End Sub

Am i on the right track ?
what i did notice was even though the variables m_TrackId and m_RaceId were declared as public
setting their value outside of the with meant they had no value at all
ace
I am not sure what your question is, but here are a few comments on what you have shown.

Even though m_TrackId and m_RaceId are declared as public in the cStarter class you still have to reference
them through the This_Starter object: This_Starter.m_TrackId.

The With cStarter statement does that.

That said, all the m_[variable] declarations should be private and accessed only through property get/let methods. If they are
not private then they are open to having their values set by direct assignment instead of only being assigned using values from the
recordsets.

Instead of opening the recordsets and leaving them open for the life of the object, the relevant private variables should be set in the
StarterID property method and the recordsets should be closed and destroyed. Think about the possible number of recordsets
that could be open if you were to create multiple cStarter objects at the same time.

For example:

CODE
Private m_DogName as string

'In the StarterId method:
If m_rstDogName.RecordCount > 0 Then
   m_DogName = m_rstDogName!DogName
End If

'Then:
Public Property Get DogName as String
  DogName = m_DogName
End Property













AlbertKallal
You have some great follow up comments.

As for using public vs private members? In those cases where you need a corresponding property get and let, and it operates on the one variable, then you might JUST AS WELL make that variable public. Having to write up tons of lets/gets is a waste of precious developer resources in these cases there are no benefits. The same goes for exposting the recordsets.

There is little wrong with exposing those recordsets as public if in fact external code needs to use those reocdsets. The idea of forcing all interaction to be via defined properites as opposed to direct use of the member variables should not be seen as an all or nothing position. If I need a list of fields from that recordset, the idea of having to code up a custom collection is far too much work and without benefit when I can just use the record set directly. You don't want to push encapsulation too far as to drive up the cost.

I seen projects where they spent $5000 just on re-sizing some text boxes and setting up un-necessary input masks - so many of these "corporate" object developers have NO idea as to the limited resources and budgets we often work with.

So from a pure point of view sure, then encapsulation is great, but from an Access point of view, we have to balance this with being practical and keeping coding costs in line. This is much like forcing all applications to be normalized to 5th order or some such. If you have a military budgets and a team of developers, sure, ok, but the flip side is we just needed to get the job done here, and without some sense of balance here, then project costs simply spin out of control.

Same thing goes for the reocrdsets. There is no need to close them and this is especially for some of them that have to iterate records. It is better to iterate them at use time, since that property many never be used until such time (saves performance at time of instancing of the object). Attempting to write and run buckets of code that anticipates what the developer going to do and need as opposed to leaving the recordsets open is a lesser evil IMHO.

Later on and over the life of the object we may need to grab additional information from that recordset, or even eventually allow editing and modifying of those values based on that open reocrdset. Having the reocrdsets closed means lots of code would have to be modified later on. This advice also applies to grabbing things like field names or even just something like how many columns exist in the recordset. I often needed this informaton to drive a combo box. And in fact my class object showen here does just that. (it maps reocdsets to combo boxes based on using the lessor known feature of combo boxes to be filled with call backs. To be fair, in this case, I don't public expose the recordset, but the recordset does remain open. In theory one could use getrows and build up a arrary or colleciton, but once again little reason exists to not use all of the built in collections and abilties that a reocrdset has.

So once again for reocrdsets that are only a one time load + grab of values, then they can be disposed of right away. However, if later on down the road we need additional columns or other information you will be RATHER sorry you disposed of those reocrdsets.

So, this again is not an all or nothing case, but one is likely better off to dispose of all of the recordsets in a common routine (the built in terminate event is a good place).

Some more comments:

Eg:
CODE
Set m_rstTotalStarts = CurrentDb.OpenRecordset("SELECT I
m_rstTotalStarts.movelast


Since we likly stick with those open recordsets, then repeating the "move last code" over and over for each property would not be required if we do it once as per above.

You code thus becomes this:

CODE
Public Property Get TotalStarts() As Integer

   TotalStarts = m_rstTotalStarts.RecordCount

End Property


And as pointed out, the above does use the reocrdset direct, and this ok since other code in the object needs to interate and test other rows of that recordset – this is a perfect example of why we leave the recordset open as opposed to closing it.

So, don't load up things un-necessary, and don't close up things un-necessary either!


This code:
CODE
Private Sub List7_Click()   
   Dim This_Starter As New cStarters   
   Dim myarray   

m_TrackId=1 'this didnt work    m_RaceId = 8078 'this didnt work


Remember, this is no different then a reocrdset.

Dim tabletest1 as dao.recordset
Dim tabletest2 as dao.recordset

In the above, if I want the "member" called recordcount, then I go:

Tabletest1.RecordCount

In your case, any member belongs to the object in question. What happens if you have 5 of your objects loaded in memory? Which one am I referring to? The WHOLE idea here is to allow you have MORE then one copy of the object in memory if you need. The WHOLE idea of an object is isolation and freedom to use that code without worry about stepping on the toes of other parts of your application that might have 3 or 5 of these starter objects in use.

So you can create and use as many of these obeject and have them ALL in memory (just like having 3 or 4 recordsets open).

So you go lit this:

This_Starter.m_RaceID

Or you can use the with as pointed out.

You should in fact see intel-sense (auto compete) display all of the properties and methods of the object when you write code (just like with a built in object). In fact, the inteli-sense during coding is another great bonus feature since then you don't have to remember all of the names and functions, since they appear in that list as a nice drop down during coding.


Also, you don't want to load up and create this whole object for just that "one" click event on your form (unless that is the ONLY place you plan to use that object). If there are several places in that form that will need that object, then JUST like how you declare a variable at the forms level (so all routines and click events can use that code in the form), you do the same with the object.

You thus declare that object at the forms level code module (not in each routine or click event).

So this suggests that in your forms load event, you have something like:

ThisStarter.StarterID = me.ID (or whatever is the starter id value).

All in all, yes, you are much on the right track .

Albert D. Kallal (Access MVP)
Edmonton, Alberta Canada
kallal@msn.com
ace
Albert,

I completely disagree with just about everything you wrote. After wading through the verbosity of the post
what I took away from it is that it is OK to be sloppy when you're using Access because it's a RAD tool.
AlbertKallal
QUOTE (ace @ Apr 9 2012, 09:13 AM) *
Albert,

I completely disagree with just about everything you wrote. After wading through the verbosity of the post
what I took away from it is that it is OK to be sloppy when you're using Access because it's a RAD tool.


You have to make specific points here and on what grounds you disagree else this just becomes an ad homonym position without merit on your part.

The idea that encapsulation is an all or nothing position is simply not a practical position to take here.

How does taking a position that prevents project completion make any sense? Not everyone can drive a 100,000 perfect maintained car. The ultimate goal can be that perfect car, but if such a car is not practical or affordable then it because a useless engineering exercise

The Chevy volt might be cool, but at $48,000, it not going to sell nor is it practical.

I fully accept that a road marching towards encapsulation is a good idea, but if such a road is perused without cost benefits, then such a road is not practical here. It not a question of being sloppy, it is a question of how far and how practical things like normalization are. An all or nothing position on these matters is without much use since such all or nothing positions don't result in affordable products, be they software, or cars.

You have to point out specific points here you disagree with, but the main advice here is to find a practical balance between methodology and costs.

For example, in most my applications I use a standard column for city when dealing with customers. A person could argue that that's not normalize correctly, and in theory one should break out that city column to another table to prevent having the CD name being repeated more than once in the database. From this theoretical point of view I wholeheartedly agree it's a better ideal, but in actual practice and development we don't do this all the time to do additional overhead and costs.

Now you can make a public statement that not normalizing the city column out to another table is sloppy programming, but I disagree, I think it's a reasonably intelligent compromise between having additional work to normalize something correctly VS. that of the practical development cost of doing so.

To ignore the cost vs benefits is something that separates great project managers from the poor ones. If you ignore this advice, then your projects are never going to be finished on time, and they're always going to be over budget.

Adopting encapsulation or adopting normalization is a good thing, but it needs to be done with cost benefits in mind. As you push normalization farther and farther, or you push encapsulation farther and farther there is LESS AND LESS benefits from an overall cost point of view.

The art of compromise is thus not being a sloppy developer, it simply the act of taking into account the practical limits of the real world we live in.

In managing projects with teams of developers, my experience is always been that the developers who can't make this compromise are the ones that spent enormous amount of times on VERY SMALL PROBLEMS and in general are the ones that are responsible for the failure of such software projects.

I'm not telling you to be sloppy, I'm telling you that as a developer you have to make intelligent compromises between a perfect ideal and the benefits of that practice in general. It's the difference between software developers that are going to get things done, and the ones that can't.

I have stated clearly the concepts of encapsulation are a great and noble goal. Such goals should always be pursued, but that pursuit of these types of goals has to be tempered with practicality.

One of the reasons why often Access projects that can be done for $5000 or $6000 cost compared to $25,000 using other development methods is in fact because those other methodologies are simply more costly!

There are developers I know that will not develop any software without writing a bunch of unit testing code. Now after spending $100,000 or more on a software project, then the benefits of such unit testing code certainly makes sense. However if your overall budget is only $500, then unit testing code is not practical in most cases and will not yield benefits.

This is the art of compromise and balance here. If your experience is different, or you have some great examples that disagree with my suggestions here, then I sure we are all most happy to entertain and hear those suggestions - and I will most graciously respect them.

The act of developing software is making these practical compromises, and in fact the very choice of using Access in of itself is one of these practical compromises with many limitations, but those limitations such as bound forms while considered a poor practice by many developers are a feature that also yields more RAD development gains then it hurts in most cases (note how I said in most cases, because in some cases there are certainly some nasty downsides).

Albert D. Kallal (Access MVP)
Edmonton, Alberta Canada
kallal@msn.com

mrpersonality
thanks for the info,both ace and albert-i will try to digest it LOL
i cant comment on whats right or wrong,best or better,i just dont have the knowledge,but i appreciate different points of view,and i dont mind trying more than one way
Albert-the click event event on the form is just my way of following the code to see what it does
its purely a tool i use to try and understand whats happening
once i can see what happens when a class is used i may then be able to determine how i will use it
the aim is to eventually select a race from the list of races presented in a list box
that race will have up to 8 starters-no more than 8,but can be less
i want to write the equations to come up with a rating for each starter based on an series of questions that can be answered by using the data stored
ideally -once the race is selected,another form will open listing the starters in order of their rating-ie from winner down to last place (well one can hope ohyeah.gif )
my program does this now-without classes,but its lots of querys and code and its getting slower and harder to manage ,and i would like to add or refine my logic
i thought classes and maybe even a collection of the class might be the best road to take to improve both my knowledge and my program
mrpersonality
Albert
using Set m_rstTotalStarts = CurrentDb.OpenRecordset("SELECT I
m_rstTotalStarts.movelast
raises an error when there are no records to move to,so i think its best if i check for records, and at least return a value if there are no records to work with
i have now created another class for Races-while its all similiar to not using classes,the intellisense does make coding easier ,and certainly from the form module ,way less calls to functions and routines,the return values seem to be displayed heaps faster as well
AlbertKallal
QUOTE (mrpersonality @ Apr 10 2012, 05:05 AM) *
Albert
using Set m_rstTotalStarts = CurrentDb.OpenRecordset("SELECT I
m_rstTotalStarts.movelast
raises an error


Yes, notice how the SELECT tapers off and is snipped. The select statement as above is also incorrect and the OpenRecordSet command you posted above also will not work!

It is simply a partial code snip and the suggestion was/is to move ALL of that move first code ETC ETC ETC to the code part where you load up the reocordsets so you don't have to repeat the movefirst/movelast a zillion times everywhere else in the code.

You still have to have the if recordcount > 0, and for any property code that has to iterate records, then have to always include a move first.

So the above was just some "air code" code here. So keep in mind that not only did that code snip leave out the "if" test for records, but if you look close the select string has no closing quotes, and is also not valid SQL (so that part as posted is also wrong).

So the suggesting here is for most of the cases you have, you can remove the moveirst/move last and clean up a good bit of code, and place such actions on the reocrdsets into one place (the place where you initial load up the reocrdsets).

It just seems there is little need to repeat the code over and over in a whole bunch of places when it can be done right after the recordset open.

And as for some difference in opinions as to best practices, you basically only have two choices

You appeal to a higher authority with books, authors and others who have been down these roads, and can produce examples. Because this is not firsthand knowledge, then in these cases you are actually having to make an act of faith here until such knowledge becomes first hand or verified through your own experiences.

In other words we are the ones bearing witness for you on these development practices, and you have to accept or reject our testimony in these matters. In fact it is actually 100% truthful and honest to state that you having to make an act of faith here!

And overtime when you experience is these practices then they'll become something other than the witness and testimony of others – it will then become YOUR own firsthand knowledge.

The overall often stated goal of using a class object is the concept of encapsulation. However by me suggesting that some of these members that you create (variables) in some developer circles is considered a bad practice to make them public.

However I have enough experience to know when these rules make sense and when these rules don't make sense. So I don't have to appeal to a higher authority, some book or some author and make an act of faith, but now have gained that firsthand experience.

In other words, in the vast majority of exposing some of these public members, you're not going to pay a penalty or cost for doing so, and as I pointed out there is actually more benefits than downsides of doing so.

In other words if somebody has an example of coding (not some reference to a book or testimony from others), but that of their OWN firsthand experience of showing why and when and how exposing those public members cost them developer time or caused bugs, then they have to step forward to demonstrate and share that experience to convince anybody of such positions.

For example, I have the frown upon using global variables to pass values between form or standard subroutines (I'm talking about general coding practices here, not class objects). In other words over the years when I have to pass some values between forms, I very rarely use global variables, and the same thing to pass values between sub/function routines. The reason is of course over the years using global variables has cost me lots of money and time, and has resulted in lost productivity. In other words I can't copy those forms, or copy the code, or have multiple instances of those forms and cold running when I utilize global variables. And if I copy such routines are forms between different applications, or even in the current application, then I have to painstakingly hunt down all of the global variable dependencies.

I had code routines that after 10+ years of extensive modifications that are well into the thousands of lines of code all of a sudden need to have another instance of that code running. And unfortunately that code had utilized tons and tons of global variables over a period of 10 years with extensive development. If we had insisted from day one that no global variables were to be used in these routines, and it was reasonably possible to do so, then we could have now had multiple instances of that code able to run and be used. Unfortunately some of the work arounds were to launch a whole another instance of the whole application, but we eventually forced buckle down and rewrote that whole module to run on local variables (and turns out that this was anon access application, and in fact was my own custom forms design package).

In other words over the years due to bad programming practices, and simply the desire to cut some corners, we did not care that we were using global variables. The result was that 10 years later we got to a point where we probably did not save any development time at all! In other words as always the developer practices you adopt must produce a tangible positive result.

In the case of exposing members of class objects? Well if it just a variable that's going to be modified in code by a get/let pair, then there is no obvious inherent benefits or even further additional encapsulation that I can see. And MORE important is over 10+ years of such a coding practice we NEVER had any practical downsides in adopting such a practice.

However if you run a team with 20 developers, and they're all going to be interactively coding writing and developing and using a set of class objects developed by many different people, it's very possible that you might have to increase the requirement and demands that developers use extra caution and tighten things up a bit in terms of how members of a class object are to be created and exposed.

Again a good developer will know when to break the rules, and when these decisions make the best sense.

If a mechanic is working on a small airplane and by accident they drop a sparkplug, they have to pick that sparkplug up and throw it in the garbage can. The reason is that dropping the sparkplug may have cracked it (they been inspected). Such a simple sparkplug failure could result down the road in a failure of the small engine. A loss of some power or engine failure in a small plane can be disastrous.

However dropping the same spark plug on the floor that you're about to change in your own lawnmower is absolutely of no consequence at all. And I suspect utilizing that spark plug in your automobile in 99% of the cases will also not be a problem. And in the leftover 1% of the cases it's really not that significant in your car or lawnmower. The risk and downside is not an issue, but in the 1% cases for an airplane, then this becomes a significant issue.

I can assure you in all cases all these development practices are not all or nothing propositions. However for those that don't have the experience that I have, then often you're going to have to appeal to some book or some development practice that says don't do this or that, and you're going to have to blindly follow that advice until you gain the experience like I have to know when such advice actually really make sense.

And I'm pretty confident public to state freely that I DO know these issues and that I have gained that knowledge and experience to make my above claims.

Like everything, the advice of being careful with that sparkplug is not the same advice that applies to your lawn mower as opposed to an aircraft.

So in terms of experiences with avoiding global variables, I can post examples and give you stories of my experience were such coding practices have cost me dearly. And I can also freely state that in every development project, exposing some of these class members as public has NEVER been a problem ever, and this is especially in the case that if it's just simply a value that's being modified by a pair of let/gets.

And in the case of exposing record sets? You can't accidentally modify a record set unless you put it into edit mode (and in fact one might eventually even want to do that). And I think if you don't need those record sets to be utilized from code outside of the class module, then I absolutely 100% agree that they should not be made public.

In other words don't be afraid to expose them if you need them! However the reverse is true, if you don't need any code outside of the class object to directly use those record sets, then there's no need it all to make them public and expose them.

Thus the terms of BEST development practices is going to be very much dependent on the CONTEXT and how your utilizing those objects and what are the resources and type of developer teams and projects you're working on.

And I have no fundamental disagreement with any of the other comments here, and they all contribute to this discussion in a great and useful way, and I welcome any other comments on the points I've made above or any others here!

Good luck!

Albert D. Kallal (Access MVP)
Edmonton, Alberta Canada
kallal@msn.com



This is a "lo-fi" version of our main content. To view the full version with more information, formatting and images, please click here.