A Question Abot DIALOGs

User avatar
James Bott
Posts: 4654
Joined: Fri Nov 18, 2005 4:52 pm
Location: San Diego, California, USA
Contact:

Post by James Bott »

Doug,

>No. But PATIENTLIST has the object of type DATAFILE that opened the PT_PATIENT datafile as a property so whilst it isn't a subclass it has many of the benefits. This was a deliberate choice partly to ease data back end migration.

>xDATAFILE Class follows. You probably need to see some of the other objects to see how it fits together.

While I applaud your efforts in creating your own class, I think you are making it more difficult than it needs to be.

Probably 98% of the cabability of your class already exists in FW's TDatabase class. You are also dealing with lots of SELECTs and in so doing you are changing the selected workarea and not returning it to where you found it. This is a problem waiting to happen.

If you use alias referencing, e.g. (::cAlias)->(dbgobottom()) in your class, then you don't need to change the selected workarea.

You have:

METHOD GoBottom() CLASS xDATAFILE
SELECT ( ::WorkArea )
GOTO BOTTOM
RETURN

This could be simplified to:

method GoBottom() class xDatafile
return (::cAlias)->(dbgobottom())

And the workarea never gets changed.

But you already get this in TDatabase AND you get the very useful buffering and all the fieldnames are defined automatically. So where is the advantage to your class? How do you handle creating temporary variables to hold data that is being edited (so the user can either cancel or save)? Hand coding?

You could have subclassed TDatabase to add your lastUpdated() method and any other new ones you wanted. This would have only taken you 10 minutes.

Once you start using TDatabase or TData, you never have to deal with workareas again.

oPatient:= TPatient():new()

msgInfo( oPatient:name )

oDoctor:= TDoctor():new()

msgInfo( oDoctor:name )

No more workareas!

As far a backend migration, a subclass is the way to go. If you subclass TDatabase to make your patient class, and later want to use recordset objects, you can create and use a TRecordset class the has all the same methods as TDatabase, and then you can switch to using recordsets by just changing the class definition of TPatientList--you just change one word.

class TPatientList from TDatabase // to use DBFs

class TPatientList from TRecordset // to use recordsets

It doesn't get much easier than that!

James
User avatar
reinaldocrespo
Posts: 918
Joined: Thu Nov 17, 2005 5:49 pm
Location: Fort Lauderdale, FL

Post by reinaldocrespo »

Here is my 2 cents:

I see you are creating pretty much a dbfclass. While you are at it; might as well add logic to the ::open, ::save... methods to manage some kind of triggers.

For example: ONpreOPEN, ONpostOPEN, ONpreINSERT, ONpostINSERT, ONpreDELETE, ONpostDELETE, ONpreUPDATE, ONpostUPDATE, to be stored on DATA elements such as: ::bOnPreSave, ::bOnPostSave, ...

This can be very powerful. I keep the "triggers" for each table stored as code blocks on a array on triggers.prg that gets compiled with all apps. On the ::open() method you ascan for any trigger defined for the specific table and assign accordingly. On_preOPEN o postOPEN you can even set the indexes for the each table and the default order, the rdd to use (I still use NTX, CDX, and ADTs), if shared, etc... . Again all coded on the codeblock. Since "triggers.prg" is linked to all you apps, they all exhibit the very same behavior. Even some business logic can be transfered to these triggers.

One thing I do with triggers is to "log" to an audit file any inserts, updates, deletes, done on some crucial tables to an "audit_log.dbf" table. All I have to do to add "logging" on a table is to add that code block to the triggers for that table.

On the other hand, if you are using ADS, then triggers can be stored on the data dictionary as well, which will make the data even more standard because the trigger will execute even when the table is modified via some other external app over which you have no control.

I specially want to point out that this is the beauty of this computer language (harbour & xharbour). Namely; code blocks and macros. I can't think of any other computer language, Delphi, or even C++, where you can do this. AFAIK, this gives us an "unfair" advantage over all other developers and is one of the reasons we can actually compete with much larger organizations. Just don't tell any one :-)

I hope I made some sense.


Reinaldo.
User avatar
xProgrammer
Posts: 464
Joined: Tue May 16, 2006 7:47 am
Location: Australia

Post by xProgrammer »

Hi Reinaldo

Glad to see others are taking an interest in our "conversation". Also the points you make are quite enlightening and rather in accordance with my approach. Indeed the standard fields I add were originally developed in pre ready access to internet/VPNs days and allowed audit trails to be exchanged overnight by modem and two servers in different geographical locations re synchronised. For each data table you had an audit table where no matter what the operation on the data table (insert, update or delete) you always do an insert into the audit trail.

Regards
Doug
User avatar
xProgrammer
Posts: 464
Joined: Tue May 16, 2006 7:47 am
Location: Australia

Post by xProgrammer »

Hi James

Have to go in less than an hour so this will of necessity be brief.

We have slightly differing philosophical approaches to OO which is healthy. And in no way do I want to imply that I'm right / you're wrong. I will explain my reasons for having chosen the architecture I have and at the same time listen to yours in the belief that if we both explain our ideas both of us and potentially others listening in will be the wiser for it.

I take your point about "scatter / gather" but please note that scatter/gather could be implemented in my architecture (not subclassing xDATAFILE class but merely pointing to it) equally as in your architecture. Part of my reason for not implementing (ceratainly the TData way - if that's the one Antonio provides) is the restriction on field names - I find the current 10 character limit already a nuisance. And auto generating from table is a no-brainer. Plus often you are in situations where you only need to read a couple of fields from a table not the lot.

To me having PATIENT (for example) inherit from xDATAFILE is using OO as a mechanism but is distorting it slightly. A Patient isn't a Table or even a record. Pragmatically this may not matter if the more natural inheritance that may exist is not required and all the natural objects map easily to single tables but I believe this approach can break down.

That's theory. Let's consider it in practice. OK take PATIENT. One object architecture I have considered in the past is to have a class PERSON and have both PATIENT and DOCTOR inherit from it.

There's code in PATIENT and DOCTOR and PATIENTFILE etc that is the same or similar and the same applies for the list variants of each so I may end up having xSINGLE and xLIST as base classes.

Some of my entities will relate to more than one table, others to none so to me deriving directly from TData or an equivalent by subclassing is potentially a problem in the long run. Also some entities are persisted partly as a table entry partly in other ways (eg a MEDICALREPORT which is a word processing document).

I understand that changing the data backend with your approach only requires a single line in each class to be changed - in practice we would use conditional compilation or some such trick to avoid this. In my approach (if you did gather/scatter) you wouldn't have to change anything at all in your classes.

I'm not suggesting you change your architecture, but hoping to explain why I have chosen mine. And hoping we both gain a deeper insight in the process.

I love discussing our craft - although we all have code to get out. Unfortunately I have to go do something else for a few days.

Regards
Doug

(We all agree on the best, most natural programming language, but our styles still differ. That makes it all the more interesting.)
User avatar
Otto
Posts: 4470
Joined: Fri Oct 07, 2005 7:07 pm
Contact:

Post by Otto »

>Part of my reason for not implementing (ceratainly the TData way - if that's the one Antonio provides) is the >restriction on field names - I find the current 10 character limit already a nuisance.

It is good that we can use longer names but be aware. Code becomes so unreadable.

From my experience I do some VB6 + NET coding, too.

From the spirits that I called. Sir, deliver me!


Regards,
Otto

Code: Select all

Using driveSearcher As New ManagementObjectSearcher(wmiQuery) 
        Using driveCollection As ManagementObjectCollection = driveSearcher.[Get]() 
            For Each moItem As ManagementObject In driveCollection 
                driveSerialnumber = Convert.ToString(moItem("VolumeSerialNumber")) 
            Next 
        End Using 
    End Using 
User avatar
Antonio Linares
Site Admin
Posts: 37481
Joined: Thu Oct 06, 2005 5:47 pm
Location: Spain
Contact:

Post by Antonio Linares »

Doug,

FWH Class TDataBase is no longer limited to 10 chars
regards, saludos

Antonio Linares
www.fivetechsoft.com
User avatar
James Bott
Posts: 4654
Joined: Fri Nov 18, 2005 4:52 pm
Location: San Diego, California, USA
Contact:

Post by James Bott »

Doug,

>We have slightly differing philosophical approaches to OO which is healthy. And in no way do I want to imply that I'm right / you're wrong. I will explain my reasons for having chosen the architecture I have and at the same time listen to yours in the belief that if we both explain our ideas both of us and potentially others listening in will be the wiser for it.

I am not trying to convince you to do things my way, I just think you are making it more difficult than it should be. One of the greatest advantages of OOP is using inheritance to make things simpler, and I think that you are not fully understanding it.

>I take your point about "scatter / gather" but please note that scatter/gather could be implemented in my architecture (not subclassing xDATAFILE class but merely pointing to it) equally as in your architecture.

I'm sure it could be, but it requires lots of coding--and it seems that you would need to hand code it for each class. If you use TDatabase or TData, there is no coding required.

>Part of my reason for not implementing (certainly the TData way - if that's the one Antonio provides) is the restriction on field names - I find the current 10 character limit already a nuisance. And auto generating from table is a no-brainer.

Again this requires you to hand code each class. Lots more work. I have never found the fieldname length to be an issue. If you really wanted to, you could still add a longer name for any field or fields, by adding them as DATA and adding modified load and save methods. So, you would only have to do a very small amount of hand coding.

Code: Select all

class TPatient from TData
   DATA veryLongFieldName
   method save
   method load
endclass

method load() class TPatient
   super:load()
   ::veryLongFieldName:= ::shortName
return nil

method save() class TPatient
   ::shortName:= ::veryLongFieldName
return nil
Thats all you need. This is much less work than hand coding a load and save method for every class. This could be hundreds or thousands of lines of code.

>Plus often you are in situations where you only need to read a couple of fields from a table not the lot.

I have never found that reading and writing all the fields caused a noticeable slowdown. We were used to only reading and writing the fields we were using in the old days, but having it all done automatically without any programming far outweighs any tiny speed gain if it actually exists. Remember that disk reads and writes are buffered so there may not be any speed gain.

Additionally, reading and writing only certain fields means that you have to have different load and save methods for each situation. This violates the polymorphism rule of OOP. You can't just call oObject:load() you have to use oObject:load1(), oObject:load2(), etc. depending on the situation. So you can't do generic things like pass any object to a function:

Code: Select all

function whatever( oObject )
   oObject:load()
return nil
>To me having PATIENT (for example) inherit from xDATAFILE is using OO as a mechanism but is distorting it slightly. A Patient isn't a Table or even a record. Pragmatically this may not matter if the more natural inheritance that may exist is not required and all the natural objects map easily to single tables but I believe this approach can break down.

I'm sorry, I don't understand what you are saying.

>That's theory. Let's consider it in practice. OK take PATIENT. One object architecture I have considered in the past is to have a class PERSON and have both PATIENT and DOCTOR inherit from it.

>There's code in PATIENT and DOCTOR and PATIENTFILE etc that is the same or similar and the same applies for the list variants of each so I may end up having xSINGLE and xLIST as base classes.

You can do this with TDatabase and TData.

Code: Select all

class TPerson from TData
   method age()
endclass

method age() class TPerson
...
return nAge

class TDoctor from TPerson
endclass

class TPatient from TPerson
endclass

msgInfo( oDoctor:age() )

msgInfo( oPatient:age() )
Not a problem.

>Some of my entities will relate to more than one table, others to none so to me deriving directly from TData or an equivalent by subclassing is potentially a problem in the long run. Also some entities are persisted partly as a table entry partly in other ways (eg a MEDICALREPORT which is a word processing document).

I am not clear on what you are saying above, but I am pretty sure you can do this using TDatabase or TData. An object can contain other objects, so for instance an invoice object can contain a line item object and a customer object. Additionally, a class could load and save data to more than one table by just creating new load and save methods.

>I understand that changing the data backend with your approach only requires a single line in each class to be changed - in practice we would use conditional compilation or some such trick to avoid this. In my approach (if you did gather/scatter) you wouldn't have to change anything at all in your classes.

By subclassing TDatabase or TData to an intermediate class (which I always recommend) you can reduce the coding change to one line (total).

class TXData from TData
endclass

Then you build all your other classes by inheriting from TXData. Then if you want to change all of them to using recordsets, you just change the one word, TData, to TRecordset and recompile.

>I'm not suggesting you change your architecture, but hoping to explain why I have chosen mine. >And hoping we both gain a deeper insight in the process.

My concern is still that you don't fully understand OOP and as a consequence you are making much more work for yourself. So far I haven't heard anything that you are doing that couldn't be done with MUCH less work using an existing database class as the base class. Many people, including myself, when first learning OOP still keep falling back into the old procedural style of programming because they don't grasp the power of inheritance.

Using inheritance, you could have created your XDatafile class like this:

Code: Select all

class XDatafile from TDatabase
   method LastUpdated()
endclass

METHOD LastUpdated() CLASS xDATAFILE 
RETURN DTOS( ::lupdated )
Isn't that a LOT less work? It has all the capability of your class and much more (including automatic gather and scatter).

>Unfortunately I have to go do something else for a few days.

We will await your return and response.

Regards,
James
User avatar
xProgrammer
Posts: 464
Joined: Tue May 16, 2006 7:47 am
Location: Australia

Post by xProgrammer »

Hi Antonio

> FWH Class TDataBase is no longer limited to 10 chars

That's good. From memory I think it was initially a 9 character limit as documented and I use all 10 allowed in the "standard" because of my naming scheme.

For example my data file might be PT_PATIENT with fields such as PT_KEY, PT_FAMLYNM, PT_GIVENNM etc.

I know xBase supports alias->fieldname but not SQL (you end up using AS) and I like ease of portability and guaranteeing all fieldnames unique is a highly desirable feature IMHO.

My variable names reflect type and so field PT_KEY would be sKey as a memory variable. It's association with the Patient object being obvious from either the :: within the PATIENT class itself or from being oPatient:sKey from outside. (Note that I distinguish between a string (s) and a character (c) - again not necessary for xBase alone. I also distinguish an integer (i) for example)

Regards
Doug
User avatar
xProgrammer
Posts: 464
Joined: Tue May 16, 2006 7:47 am
Location: Australia

Post by xProgrammer »

Hi James

>> I take your point about "scatter / gather" but please note that scatter/gather could be implemented in my architecture (not subclassing xDATAFILE class but merely pointing to it) equally as in your architecture.

> I'm sure it could be, but it requires lots of coding--and it seems that you would need to hand code it for each class. If you use TDatabase or TData, there is no coding required.

No - only once - in xDATAFILE

> Additionally, reading and writing only certain fields means that you have to have different load and save methods for each situation. This violates the polymorphism rule of OOP. You can't just call oObject:load() you have to use oObject:load1(), oObject:load2(), etc. depending on the situation. So you can't do generic things like pass any object to a function

It doesn't stop you from having a generic oObject:load(). But if you truly followed that mantra you couldn't possibly write any system with any level of complexity using SQL if you wanted any reasonable level of performance.

>>Some of my entities will relate to more than one table, others to none so to me deriving directly from TData or an equivalent by subclassing is potentially a problem in the long run. Also some entities are persisted partly as a table entry partly in other ways (eg a MEDICALREPORT which is a word processing document).

> I am not clear on what you are saying above, but I am pretty sure you can do this using TDatabase or TData. An object can contain other objects, so for instance an invoice object can contain a line item object and a customer object. Additionally, a class could load and save data to more than one table by just creating new load and save methods.

Precisely! An object can contain other objects. Thats what mine do. So the PATIENT Class contains

DATA oDbf

which is an object of type xDATAFILE

A PATIENT_REPORT class might (if I were coding in Windows) contain:

DATA oDbf
DATA oWordDoc

In effect we are doing the same thing only you are using subclassing as a mechanism, I am using pointers to other objects as my preferred mechansim. I think mine is cleaner, more flexible, more transportable and more reflective of the real world. You may well think otherwise, but in reality the difference either way is small. But I don't like to encourage people to think in terms of data files rather than objects which I think subclassing most objects from a data file class may do in the case of people less experienced than yourself.

> My concern is still that you don't fully understand OOP and as a consequence you are making much more work for yourself.

I think you may have misjudged me based on my decision not to do automated scatter/gather and to write my own xDATAFILE Class. Both were conscious decisions and neither really justify your conclusion. Quite some years ago now I wrote a dynamic web application development/delivery architecture that led me to a structure that followed OO mechanisms in a stateless environment and reinforced the value of this approach in my thinking. (That may not make a lot of sense but to explain it fully would take too long).

Clearly xDATAFILE or any equivalent is pretty fundamental to any application and I was happier using one I wrote, and so understood. It did also, interestingly enough, require a slight modification when I moved to linux, otherwise backups were incomplete if a user was still logged in to the software (even in its single user guise). It also only took a few hours maximum to write and test.

I would like to repeat that a patient (represented by an object of class PATIENT) is not an example / subclass / whatever of a data table record (represented by part of an object of type xDATAFILE) although there is clearly a corellation between them. Furthermore a list of patients is a different object from a single patient and I believe should be different classes. If they are in the one and the same class you would, it seems to me, be in flagrant contradiction of your own polymorphism mantra - what does oObject:load() do - load one or load a list?

Using classes and inheritance gives a range of advantages. True they can cut coding and solve problems of variable "life" (sorry - can't remember technical term) largely removing the need for public variables. And you have obviously fervently used them for these purposes. But there is also another aspect as to how they relate to their counterparts in the real world and maybe you aren't seeing their full potential in that regard.

Regards
Doug
User avatar
xProgrammer
Posts: 464
Joined: Tue May 16, 2006 7:47 am
Location: Australia

Post by xProgrammer »

Hi Otto

> It is good that we can use longer names but be aware. Code becomes so unreadable.

I couldn't agree more! I hate ThoseFunctionsWithImpossiblyLongNames()

Personally I would be happier with a 12 or perhaps 16 character limit as witha 10 limit I effectively restrict myself to 7 characters due to my naming scheme (which started out with corporate work with SQL where there is no such limit).

I also like to keep comments relatively short and minimalist. Years ago I read a study that showed that professional programmers could debug other people's code more rapidly if given code with all the comments stripped out!. The authors surmised that the human brain is limited as to the size of the structure it can understand and lots of comments make it too hard to "see" how the code is actually structured.

But when I had to suffer a university course in programming (as part of a commerce degree) they taught the exact opposite. The first assignment was to generate a Fibonacci series ( 1 1 2 3 5 8 13 21 etc). Soon overflows. So I wrote a set of routines to handle 120 digit integers and produced a listing of all Fibonacci numbers up until the 120 digit limit was exceeded and then exited gracefully with a message. I got less marks than a person whose program didn't work at all - it couldn't even generate the first 5 Fibonacci numbers. But according to our lecturer it was well documented! So I knew the rules and got full marks thereafter. But I also began to realise why the graduates turned out by our universities have to be re-trained if they are going to be much use at all.

Regards
Doug
User avatar
James Bott
Posts: 4654
Joined: Fri Nov 18, 2005 4:52 pm
Location: San Diego, California, USA
Contact:

Post by James Bott »

Doug,

>>> I take your point about "scatter / gather" but please note that scatter/gather could be implemented in my architecture (not subclassing xDATAFILE class but merely pointing to it) equally as in your architecture.

>> I'm sure it could be, but it requires lots of coding--and it seems that you would need to hand code it for each class. If you use TDatabase or TData, there is no coding required.

>No - only once - in xDATAFILE

Hmm, can you show us a small example? I am having a hard time seeing how you could write a generic gather/scatter routine. I would think that you would have to create data elements for each fieldname and assign them in both the gather and scatter methods and this would have to be done for each class. And you would have to define all the data elements in the table class also.

Code: Select all

method load() Class TPatientList
   ::name:= ::cAlias->PT_NAME
   ::city:= ::cAlias->PT_CITY
return nil

method load() Class TPatient
   ::name:= ::oPatientList:name
   ::city:= ::oPatientList:city
return nil
With TDatabase and TData fieldname are automatically generated as DATA so you can do:

oPatientList:= TData():new(,"patients")
oPatientList:use()

msgInfo( oPatientList:name ) // returns James

And then you can change the data in the name var:

oPatientList:name:= "Doug"

msgInfo( oPatientList:name ) // returns Doug

This buffered data, the disk record still contains "James" until you do a save(). So you can use these in controls and if the user selects OK you do a oPatientList:save() otherwise you do nothing and the disk record remains as it was.

Note that there were only two lines of code needed to get this object by using the generic TData class. We can use the same code and only change the filename to get a different object and it knows all the fieldnames of that database.

oOrderList:= TData():new(,"orders")
oOrderList:use()

msgInfo( oOrderList:orderID )

A subclass for a patientlist class could be as simple as:

Code: Select all

class TPatientList from TData
   method new()
endclass

method new() class TPatientlist
   super():new(,"patients")
   if ::use()
      ::setOrder(1)
      ::gotop()
   endif
return self
Then you can do:

oPatientList:= TPatientlist():new()

msgInfo( oPatientlist:name )

>> Additionally, reading and writing only certain fields means that you have to have different load and save methods for each situation. This violates the polymorphism rule of OOP. You can't just call oObject:load() you have to use oObject:load1(), oObject:load2(), etc. depending on the situation. So you can't do generic things like pass any object to a function.

>It doesn't stop you from having a generic oObject:load(). But if you truly followed that mantra you couldn't possibly write any system with any level of complexity using SQL if you wanted any reasonable level of performance.

If you use a TRecordset class then you can select whatever fields you wish:

oPatientList := TRecordset():new( "SELECT name, city FROM Patientlist , "Provider=Microsoft.Jet.OleDB.4.0;Data Source=Northwind.mdb" )

oPatientList:city:="San Francisco"
oPatientList:save()

The recordset contains only the fields you specify and the save() method only updates those fields. Again here we have used nothing but a generic TRecordset class.

...

>In effect we are doing the same thing only you are using subclassing as a mechanism, I am using pointers to other objects as my preferred mechanism. I think mine is cleaner, more flexible, more transportable and more reflective of the real world. You may well think otherwise, but in reality the difference either way is small.

OK. However, I am still having a hard time grasping your design.

>But I don't like to encourage people to think in terms of data files rather than objects which I think subclassing most objects from a data file class may do in the case of people less experienced than yourself.

I agree with you and I did discuss this in one of the articles on my website. There is a difference between a patient object and a patient table. I have not discussed this much on the forum because I have a hard enough time getting others interested in using OOP without confusing them too much.

>> My concern is still that you don't fully understand OOP and as a consequence you are making much more work for yourself.

>I think you may have misjudged me based on my decision not to do automated scatter/gather and to write my own xDATAFILE Class. Both were conscious decisions and neither really justify your conclusion.

OK, are you saying that you are having to write more code but you think it is worth it, or are you saying the you don't have to write more code with your design?

>I would like to repeat that a patient (represented by an object of class PATIENT) is not an example / subclass / whatever of a data table record (represented by part of an object of type xDATAFILE) although there is clearly a correlation between them. Furthermore a list of patients is a different object from a single patient and I believe should be different classes. If they are in the one and the same class you would, it seems to me, be in flagrant contradiction of your own polymorphism mantra - what does oObject:load() do - load one or load a list?

As with other code there are tradeoffs. I also have a TRecord class which I use as the base of a TPatient class.

oPatientList:= TData():new(,"patients")
oPatientList:use()

msgInfo( oPatientList:name )

Or you can do (in addition to the above):

oPatient:= TRecord():new( oPatientList )

msgInfo( oPatient:name )

For some routines it is just faster to use the table class only.

The generic TRecord class only has new(), load(), save(), and end() methods. It also automatically adds all the fieldnames as DATA. Normally, I would subclass this and add any methods specific to a patient, like age() for instance.

Like yours, my patient class would contain in instance of the patient table. The patient object reads and writes its data to the table object. The big difference is that mine automatically generates fieldnames as DATA and they are buffered. This is inherited.

You could have an instance of a record class in the table class but I fear it would slow it down when you are doing browses and reports. Is this how you are doing them?

>Using classes and inheritance gives a range of advantages. True they can cut coding and solve problems of variable "life" (sorry - can't remember technical term) largely removing the need for public variables. And you have obviously fervently used them for these purposes. But there is also another aspect as to how they relate to their counterparts in the real world and maybe you aren't seeing their full potential in that regard.

Yes, I don't use any publics. And I do design my objects based real-world objects--I am a big advocate of doing this. Glad to hear that you are also.

Are you saying you never use inheritance?

Regards,
James
User avatar
xProgrammer
Posts: 464
Joined: Tue May 16, 2006 7:47 am
Location: Australia

Post by xProgrammer »

Hi James

Our techniques vary, but we are actually more similar than someone casually reading these posts might imagine.

Yes I do use inheritance, but somewhat less than you in that I use xDATAFILE via class properties rather than direct inheritance.

I don't currently use any form of generic scatter / gather but I do buffer all data and updates are only written after the user confirms that they want to update / write. (Actually I mostly make user select record in read only mode and hit an edit button in order to go into edit mode - slows things down a bit but is safer.)

The only public variables I use relate to my objects themselves, and maybe they should be in an application class? Currently, however, I have:

Code: Select all

PUBLIC oPatient
dbfPATIENT  := xDATAFILE():New(  1,  1, "PT", "PT_PATIENT",  "Patient Master",      .T., .T., "PT_KEY", "PT_NAME" )
dbfPATIENT:OpenIndexed()
oPATIENT      := PATIENT():Create( dbfPATIENT )
This means that any class can access the currently selected patient, for example, without having to pass the patient object to it. Maybe you have a suggestion?

Regards
Doug
User avatar
James Bott
Posts: 4654
Joined: Fri Nov 18, 2005 4:52 pm
Location: San Diego, California, USA
Contact:

Post by James Bott »

Doug,

>I do use inheritance, but somewhat less than you in that I use xDATAFILE via class properties rather than direct inheritance.

I'm still confused. You said you had a patientList class but below you are just using an instance of the xDATAFILE class as the list object. Can we see your patientList class? When is it used instead of an instance of xDATAFILE?

One advantage of using inheritance for your patientList class is that this line:

dbfPATIENT := xDATAFILE():New( 1, 1, "PT", "PT_PATIENT", "Patient Master", .T., .T., "PT_KEY", "PT_NAME" )

Can become this:

oPatientList:= TPatient():new()

Your patientList class is simply:

Code: Select all

class TPatientList from xDATAFILE
   method new()
endclass

method new() class TPatientList
  super():new(  1,  1, "PT", "PT_PATIENT",  "Patient Master",      .T., .T., "PT_KEY", "PT_NAME" ) 
return self
The calling code is shorter, easier to read, and if you later want to change any of the parameters that you use to create the patientList object, you can change them in one place rather than everyplace that you create a patient list as you have to do now.

Also, you can use inheritance to override a method for one class. Say you want to record the last date and time the record was saved, but only for the patient records. You can inherit from xDATAFILE and add a new method save() which records the date and time. Yes, you could manually assign these values when you save the data, but you have to remember to do it whenever you save a record. If it is built into the save method it only has to be coded once.

Code: Select all

method save() class TPatient
   ::lastDate:= date()
   ::lastTime:= time()
return super():save()
>I don't currently use any form of generic scatter / gather but I do buffer all data and updates are only written after the user confirms that they want to update / write. (Actually I mostly make user select record in read only mode and hit an edit button in order to go into edit mode - slows things down a bit but is safer.)

OK, so you do have to hand code every scatter and gather routine.

Making the user select Edit is definitely not a Windows standard and does create more work for the user. It is one of our jobs as programmers to make less work for the user. I don't see why it is safer; unless the user presses the OK button, any changes they made (intentionally or accidentally) will not be saved.

>The only public variables I use relate to my objects themselves, and maybe they should be in an application class? Currently, however, I have:

>Code:
>PUBLIC oPatient
>dbfPATIENT := xDATAFILE():New( 1, 1, "PT", "PT_PATIENT", "Patient Master", .T., .T., "PT_KEY", "PT_NAME" )
>dbfPATIENT:OpenIndexed()
>oPATIENT := PATIENT():Create( dbfPATIENT )

Is there a reason you used Create() as your constructor method rather than the standard New()? This is a violation of the polymorphism principle.

>This means that any class can access the currently selected patient, for example, without having to pass the patient object to it. Maybe you have a suggestion?

One way is to create a set/get function that contains a static object var.

Then you can do:

getPatient( Patient():Create( dbfPatient ) )

Now you can do:

local oPatient := getPatient()

msgInfo( oPatient:name )

But since the getPatient function returns an object you can use this shorter syntax and you don't have to create a local.

msgInfo( getPatient():name )

OK, that is one solution but it is very limited. Assuming you either now have, or will have, other values that you need global access to, it would be better to create a global class, TSystem, and include oPatient as one of the DATA elements. You still store the oSystem object in a set/get function and then you have access to all the system data anywhere in the program without any PUBLICS.

However, I don't see why you want to avoid having to pass a patient object to another object. OOP is supposed to work by passing objects. There is not much coding difference between:

patientReport():new()

and,

patientReport():new( oPatient )

And there are cases where it is more code:

Code: Select all

do while ! oPatientList:eof()
   oPatient:=  := PATIENT():Create( oPatientList )
   patientReport():new( )
enddo
Or,

Code: Select all

do while ! oPatientList:eof()
   patientReport():new( Patient():Create( oPatientList ) )
enddo
Also, it can get confusing if you have both a public oPatient and a local oPatient.

Another issue that it is a violation of the encapsulation principle of OOP. Any given method should not have to rely on the public var oPatient to contain the patient object that it may need. Suppose that you have the public oPatient set and then you call another object to execute a routine that requires it to lookup another patient record. Does the other routine assume the that the public oPatient contains the record it needs, or does it change oPatient to the one it needs? With encapsulation the routine assumes nothing and the calling routine doesn't need to do anything before calling the other routine.

It still seems to me that you are falling back into procedural programming principles when OOP can serve you better.

Regards,
James
User avatar
xProgrammer
Posts: 464
Joined: Tue May 16, 2006 7:47 am
Location: Australia

Post by xProgrammer »

Hi James

I really need some time to digest all your comments!

However I was lying in bed this morning musing over why two programmers with, I think, fairly similar fundamental beliefs, have xBase OOP styles that are fairly different. (just woken up!)

Two of the influences on me that have had an impact on my style are:

1. Three tier clent/server programming (that Microsoft was pushing) which amongst other things called for the separation of UI, business rules, and persistence. The UI and business rule layers had to be independant of how and where the data was stored.

2. The shift to xml for data transport with style separated out as CSS or XSTL.

When I wrote my web application delivery technology there was a translation capability so that the calling object could use different tag names from the object that it called. (Data was passed as xml.)

Whilst my code isn't exactlly three tier, it does pretty much conform to the general idea. If you use automated scatter/gather as I understand its current form, then your UI layer has to be aware of the field names that data is stored under. That becomes a problem when you try and hook into existing data or existing objects. If the software you are writing will ever only be a standalone application then it isn't an issue.

Also, unlike you, whilst I am not new to xBase, a windows GUI or OOP but to xBase with either OOP or a windows GUI let alone both. So I am coding my objects pretty much by hand from scratch. Then as patterns occur the degree to which I harness the power of inheritance will increase.

Regards
Doug
User avatar
James Bott
Posts: 4654
Joined: Fri Nov 18, 2005 4:52 pm
Location: San Diego, California, USA
Contact:

Post by James Bott »

Doug,

>The UI and business rule layers had to be independant of how and where the data was stored.

This is a good point and I was trying to show how that was accomplished. I have a TData class for DBF access, a TRecordset class for SQL recordset access, and a TArray class for array access. These all were written using polymorphism, so they all have the same methods and data. Any class can be substituted for the other so you can access data in all those formats. I haven't attempted to write a TXML class yet, but I am sure it could be done.

Routines that use tables, like browses and reports, are not affected by switching the datasource class.

The business rules would exist either in the table subclass of any of the above data source classes, and in the real-world objects such as Patient.

So the data source is independent of the business rules.

Using inheritance, the table subclasses can be very small. Anther OOP principle is to move as much code as possible as far up the inheritance ladder as possible. This means you have to write less code in all the subclasses.

James
Post Reply