Resolved Explain property and data member differences and when to use final

progdev1

Member
Hi all,
I have been asked to do some coding on a project that uses classes. It has been a while since I did OOP in college and when I did before it was with java. I am struggling to understand a few concepts and I have a few questions that maybe one of you could answer.

Just to explain what happened before:
At the front end I run appserver procedure and pass it a output temp-table which mirrors an actual database table .
At the back end the app server procedure populates the received temp table with data from the actual table and passes the temp table to the front end to display which displays the table in a browser program.
At the front end every time the user adds, deletes or modifyies a record. A procedure on the app server is run and the relevant detail are passed to the back end. E.g. run deleteEmployee on ... (ipiEmpID).

I am trying to model the above behaviour with using classes.
On the app server I intend to have a class to replace the back end procedure
Example (which runs with sports2000 database).
Say I have a Table Employee in a database defined as.
Code:
EmpNum                      inte       
LastName                    char       
FirstName                   char       
Birthdate                   date         
StartDate                   date

Code:
USING Progress.Lang.*.
CLASS Employee FINAL: 
    DEFINE PRIVATE TEMP-TABLE tt-Employee NO-UNDO
      FIELD EmpNum    AS INTEGER
      FIELD FirstName AS CHARACTER
      FIELD LastName  AS CHARACTER 
      FIELD BirthDate AS DATE
      FIELD StartDate AS DATE
    INDEX PrimIdx  IS PRIMARY EmpNum .
    /**
    * Called automatically to initialise the class before the instance is created or any static 
    * members are referenced. Cannot be called directly. Typical use is to initialise logging.
    */
    CONSTRUCTOR STATIC Employee ():

    END CONSTRUCTOR. 

   METHOD PUBLIC HANDLE GetEmployeeList():       
        EMPTY TEMP-TABLE tt-Employee.

        FOR EACH Employee NO-LOCK:
            CREATE tt-Employee.
            BUFFER-COPY Employee TO tt-Employee.
        END.

        RETURN TEMP-TABLE tt-Employee:HANDLE.
   END METHOD.

   METHOD PUBLIC LOGICAL SetEmployee (INPUT ipiEmpNum  AS INTEGER,
                                      INPUT ipchFName  AS CHARACTER,
                                      INPUT ipchLName  AS CHARACTER,
                                      INPUT ipdaDOB    AS DATE, 
                                      INPUT ipdaStart  AS DATE):
      FIND FIRST Employee WHERE Employee.EmpNum = ipiEmpNum EXCLUSIVE-LOCK NO-ERROR NO-WAIT.
      IF NOT AVAILABLE (employee) THEN DO:
          CREATE Employee.
          ASSIGN Employee.EmpNum    = ipiEmpNum.
      END.

      ASSIGN Employee.FirstName = ipchFName
             Employee.LastName  = ipchLName
             Employee.BirthDate = ipdaDOB
             Employee.StartDate = ipdaStart.
   END METHOD.

   METHOD PUBLIC CHARACTER GetEmployeeName (INPUT ipiEmpNum AS INTEGER):
       DEFINE VARIABLE vchName AS CHARACTER NO-UNDO. 
       ASSIGN vchName = "".
       FIND FIRST Employee WHERE employee.EmpNum = ipiEmpNum NO-LOCK NO-ERROR.
       IF AVAILABLE Employee THEN
           ASSIGN vchName =  Employee.FirstName + " " + Employee.LastName.
       RETURN vchName.
   END METHOD.
   DESTRUCTOR Employee():
       EMPTY TEMP-TABLE tt-Employee.
   END.
END CLASS.

Say I run this at the front end using
Code:
DEFINE TEMP-TABLE tt-Employee NO-UNDO
      FIELD EmpNum    AS INTEGER
      FIELD FirstName AS CHARACTER
      FIELD LastName  AS CHARACTER 
      FIELD BirthDate AS DATE
      FIELD StartDate AS DATE
    INDEX PrimIdx  IS PRIMARY EmpNum .

DEFINE VARIABLE clsEmployee     AS Employee  NO-UNDO.
DEFINE VARIABLE vchEmployeeName AS CHARACTER NO-UNDO.
DEFINE VARIABLE vhdlTTTable    AS HANDLE    NO-UNDO.
DEFINE VARIABLE vhdlTTEmployee AS HANDLE    NO-UNDO.

clsEmployee = NEW Employee().

ASSIGN vhdlTTTable    = clsEmployee:GetEmployeeList()
       vhdlTTEmployee =  TEMP-TABLE tt-Employee:HANDLE.
   
vhdlTTEmployee:COPY-TEMP-TABLE(vhdlTTTable).
/* In a real siutation this would be populating a browse.  */
FOR EACH tt-employee NO-LOCK:
    DISP tt-employee.
END.
MESSAGE vhdlTTTable VIEW-AS ALERT-BOX.

I have a few questions.
1. Is there any point in having methods such as GetEmployeeName given I pass the temp table to the front end. I would think not but I am not sure.
2. I am not sure I have fully understood the concept of Final in OOP. I know a final Class cannot be inherited. I have declared the class as final because it updates the database and I don't want other classes inheriting an employee, overloading it and updating the data in the employee table. Is this correct or is this overkill?
3. Could someone please explain the difference between a property and a data member. I don't understand what the difference between a data member (such as my temp table) and a property is. It seems almost duplication of a data member. Why would I ever would ever want to use a property.

Can anyone point me if I am on the right track here please.
 

GregTomkins

Active Member
We don't use Progress OOP, so I don't really know what I'm talking about.

But I think the basic idea is that instead of 'METHOD PUBLIC HANDLE GetEmployeeList()' you could use 'DEFINE PUBLIC PROPERTY EmployeeList AS EmployeeListClass', where 'EmployeeListClass' is (basically) a class wrapper around a temp-table. Users of your class can then say things like 'MESSAGE EmployeeInstance.EmployeeListclass.EmployeeName' rather than calling GetEmployeeList, assigning its output to a variable, and then MESSAGEing that. If you use non-OOP Progress, that's like running an internal procedure vs. calling a function (not much difference).

Your question #1 has been debated in JavaLand since the beginning of time. By far the most prevalent opinion is that you should never give callers access to your data members directly, eg., you should always use getters/setters/properties, so that you can (a) enforce invariants, eg. that a birth date can't be in the future, and (b) change things afterwards without disrupting callers. My opinion is that about 50% of all the Java code on earth is boilerplate getters and setters that may be IDE-generated and easily understood, but adds to the overhead of understanding and maintenance in often pointless ways. But that's just my opinion ;)

If you have a few hours to spare and are prepared to come out of it with a thousand vociferous opinions and no clear direction, you could post these questions on Stack Overflow ;) (SO is awesome, but questions like these tend to devolve into highly opinionated religious wars with tons of super-smart people on both sides of the argument).

By the way, this may just be me being anal about names and not reading your code closely enough, but it seems illogical to have a class called 'Employee' that returns a LIST of Employees. I would think Employee is the individual element of a list of employees (like a temp-table or DB record), and the thing that returns the list is called 'EmployeeListFinder' or something like that.
 

progdev1

Member
Hi,
Thanks for your input I agree with you regarding Stack overflow and it might make me even more confused. As regards .
By the way, this may just be me being anal about names and not reading your code closely enough, but it seems illogical to have a class called 'Employee' that returns a LIST of Employees. I would think Employee is the individual element of a list of employees (like a temp-table or DB record), and the thing that returns the list is called 'EmployeeListFinder' or something like that.
I kind of agree with you on this but I was not too sure how to approach this. To be honest I still am not. I would prefer not to have to create a separate class for retrieving temptable data as it would result in too much program fragmentation. Are you are saying creating a property (option 2 below) is okay in OOP and saves me from having to create a separate wapper Class (option 1 below). If so I can now see the point of a property and will probably use that. If you are saying that option 1 really is what should be done. Then fair enough I guess.


Code:
/* OPTION 1 */
USING Progress.Lang.*.
CLASS EmployeeList FINAL:
  DEFINE PRIVATE TEMP-TABLE tt-Employee NO-UNDO
  FIELD EmpNum  AS INTEGER
  FIELD FirstName AS CHARACTER
  FIELD LastName  AS CHARACTER 
  FIELD BirthDate AS DATE
  FIELD StartDate AS DATE
  INDEX PrimIdx  IS PRIMARY EmpNum .
....
  
 METHOD PUBLIC HANDLE GetEmployeeList():  
  EMPTY TEMP-TABLE tt-Employee.
  FOR EACH Employee NO-LOCK:
  CREATE tt-Employee.
  BUFFER-COPY Employee TO tt-Employee.
  END.
  RETURN TEMP-TABLE tt-Employee:HANDLE.
  END METHOD.
END CLASS.
/* OPTION 2 */
CLASS Employee FINAL: 
DEFINE TEMP-TABLE tt-Employee NO-UNDO
  FIELD EmpNum  AS INTEGER
  FIELD FirstName AS CHARACTER
  FIELD LastName  AS CHARACTER 
  FIELD BirthDate AS DATE
  FIELD StartDate AS DATE
  FIELD EmpRowid  AS ROWID 
  INDEX PrimIdx  IS PRIMARY EmpNum .
  DEFINE PROPERTY EmployeeList AS HANDLE NO-UNDO
  GET():
  EMPTY TEMP-TABLE tt-Employee.
  
  FOR EACH Employee NO-LOCK:
  CREATE tt-Employee.
  BUFFER-COPY Employee TO tt-Employee.
  ASSIGN tt-Employee.EmpRowid = ROWID(Employee).
  END.
  
  RETURN TEMP-TABLE tt-Employee:HANDLE.
  END.
  END GET.
  METHOD PUBLIC LOGICAL SaveEmployee( INPUT iphdlbuffer AS HANDLE ):
  ...
  METHOD PUBLIC LOGICAL DeleteEntity( ipridEnity AS ROWID ):
   ....
END CLASS.
 

tamhas

ProgressTalk.com Sponsor
You are treading on some contentious ground here ... strong feelings abound. TTs are a very powerful and useful part of ABL, but they are not very OO since they are inherently relational. This leads to many different usage patterns, some based on a clear theoretical position and some based on little more than laziness.

You actually have a couple of different issues here. Let me start with the notion that the "Good OO" way to use TTs is to wrap them in a class and provide needed members to do the work required. The core principle here is that the implementation in a class should be invisible to anything outside the class, i.e., nothing outside should know whether you have used a TT or arrays or a linked list or a collection of objects internally.

A second issue is passing a set of data across an AppServer boundary. In the old days, pre OO and a bunch of other things, one would naturally do this by passing a TT ... so, lots of people still want to do it with a TT. That tends to lead to your nice encapsulated TT per the prior paragraph having a method which is essentially "GiveMeYourInternalImplementation". From an OO perspective, that is pretty ugly, but, what to do. Fortunately, these days one of the options is to use the built-in methods on a TT to serialize it to XML or JSON. Now, you might be asking, why would I serialize to JSON, send it over the wire, and then deserialize at the other end when I could just send the TT. For starters, it might surprise you at how efficient it is. But, more importantly, today you might have ABL at both ends, but how long is that going to be always true? If the other end is not ABL, wouldn't it be much better to have a serialization packet which was technology neutral?

On properties vs data members. There is a debate in the OO world about directly accessing public data members. The argument against is that one is exposing the internal implementation directly and provides control like read or write only. The argument for is that it keeps your code from being laden with getters and setters and is much more direct. Properties gives you the best of both. You can use them in external code as if they were simple public data members, but internally you have complete control over what happens in a get and set so that internal form and external form can be entirely different and you can provide any access control you want. Importantly, you can change that internal representation without changing the external references. So, I use properties for anything public and define variable for anything internal.

You seem to have the right general idea for FINAL.

You might find some useful reading here http://cintegrity.com/content/OOABL
 

progdev1

Member
You actually have a couple of different issues here. Let me start with the notion that the "Good OO" way to use TTs is to wrap them in a class and provide needed members to do the work required. The core principle here is that the implementation in a class should be invisible to anything outside the class, i.e., nothing outside should know whether you have used a TT or arrays or a linked list or a collection of objects internally.
Thank you for the link, it is quiet long so I'll read it in due course. Thanks for your tip re: JSON and the appserver that makes sense.

I am a bit confused regarding the wrapper class for the temp table. I understood the concept of having a wrapper class in that an an employee is an object and the temp table is a collection of objects and we don't want the wrapper class knowing the internal working of the instantiated class (Employee Class). Where I am having the difficulty is figuring out how that wrapper class works in practice.

Say I have a wrapper class called EmployeeList. I would assume my EmployeeList class shouldn't have a method that has references to the actual tables e.g.
Code:
METHOD PUBLIC HANDLE GetEmployee( ):
    For each employee
       Create tt-Employee. 
       BUFFER-COPY Employee TO tt-Employee.
    end.

I would also assume that I would not have a get method for each field in the table as that wouldn't make sense. e.g.:
Code:
CLASS EmployeeList FINAL:
  DEFINE PRIVATE TEMP-TABLE tt-Employee NO-UNDO
   ...
   create tt-Employee.
   assign tt-Employee. EmpNum = cEmployee:GetEmployeeNum(input ipriEmployee AS ROWID)
              tt-Employee. FirstName = cEmployee:GetEmployeFirstName(input ipriEmployee AS ROWID)
              tt-Employee. LastName = cEmployee:GetEmployeLastName(input ipriEmployee AS ROWID)      .... etc

In terms of populating the temp table then the only option I see open to me is to; use a query in Employee Class and to pass back a buffer to the EmployeeList class and populate the temp table from it and use that temp-table to create the json and pass it back to the front end.

So my new implementation of Employee would look something and EmployeeList like this . Is this roughly what you were referring to?
Code:
USING Progress.Lang.*.
CLASS EmployeeList FINAL:
  DEFINE PRIVATE TEMP-TABLE tt-Employee NO-UNDO
  FIELD EmpNum  AS INTEGER
  FIELD FirstName AS CHARACTER
  FIELD LastName  AS CHARACTER
  FIELD BirthDate AS DATE
  FIELD StartDate AS DATE
  INDEX PrimIdx  IS PRIMARY EmpNum .

  DEFINE VARIABLE viCnt AS INTEGER NO-UNDO.
....
     CONSTRUCTOR PUBLIC EmployeeList ():
        EMPTY TEMP-TABLE tt-Employee.
        DEFINE VARIABLE cEmployee as Employee no-undo .
        DEFINE VARIABLE iNumEmployees as INTEGER NO-UNDO. 
        ASSIGN cEmployee          = new Employee()
                       iNumEmployees = cEmployee:NumEmployees. /* */
        REPEAT viCnt = 1 to iNumEmployees:
                 CREATE tt-Employee.
                 ASSIGN vhdlBuffer = BUFFER tt-Employee:HANDLE.
                IF VALID-HANDLE(vhdlBuffer)
                     vhdlBuffer:BUFFER-COPY(cEmployee:GetEmployee()).
                ELSE
                         ASSIGN viCnt = iNumEmployees.
        END. 
    END CONSTRUCTOR.
END CLASS.

USING Progress.Lang.*.

CLASS Employee:
    DEFINE VARIABLE vhQuery AS HANDLE NO-UNDO.
      
    DEFINE PUBLIC PROPERTY NumEmployees AS INTEGER  NO-UNDO
    GET():
        DEFINE VARIABLE viNumResults AS INTEGER NO-UNDO.
        IF VALID-HANDLE(vhQuery) THEN
            ASSIGN viNumResults = vhQuery:NUM-RESULTS.
        ELSE
             viNumResults = 0.
        RETURN viNumResults.
    END GET.
  
    /**
    * Called automatically to initialise the class before the instance is created or any static
    * members are referenced. Cannot be called directly. Typical use is to initialise logging.
    */
    CONSTRUCTOR PUBLIC Employee ():
        
        CREATE QUERY vhQuery.
        vhQuery:SET-BUFFERS ("Employee").
        vhQuery:QUERY-PREPARE( "FOR EACH Employee NO-LOCK ").
        vhQuery:QUERY-OPEN ().
      
    END CONSTRUCTOR.
  
    METHOD PUBLIC HANDLE GetEmployee( ):
      
        DEFINE VARIABLE result AS HANDLE NO-UNDO.
        IF vhQuery:QUERY-OFF-END = FALSE THEN DO:
            vhQuery:GET-NEXT ().
            RETURN BUFFER Employee:HANDLE.
        END.
        ELSE
            RETURN ?.

    END METHOD.

METHOD PUBLIC LOGICAL DeleteEmployee( INPUT iprwEmployee AS ROWID  ):
        ....      
  
   METHOD PUBLIC LOGICAL SaveEmployee (INPUT ipiEmpNum  AS INTEGER,
                                       INPUT ipchFName  AS CHARACTER,
                                       INPUT ipchLName  AS CHARACTER,
                                       INPUT ipdaDOB    AS DATE,
                                       INPUT ipdaStart  AS DATE):
    ....
  
END CLASS.
 

tamhas

ProgressTalk.com Sponsor
Different people have different ideas on this, but let me make a few suggestions from the perspective of keeping the best OO principles. First of all, there are no blanket rules because a lot depends on what you are doing. In an OO3GL, one would typically have Employee objects and your EmployeeList would simply be a collection and the collection would be a property of some task object that was going to do something with that collection. But, the "do something" part is important here because there are many different kinds of tasks and the best solution will depend on the context. For example, why is there a collection to start with? Are we doing something where we can't just process the employees individually? Is this group something like all the people in a department and we are doing something complicated with the set as a whole?

This kind of context is important in OOABL because we have a combination of it being relatively expensive to create objects, so creating lots of them we don't really need is contraindicated, and we have temp-tables and ProDataSets which are relational, not OO, but which are still very good ways to handle sets of data.

But, one of the key points of encapsulation is to put the operations on the set inside the class. I.e., we are not merely encapsulating the data, but also the operations. That way, the way the operation is fulfilled is invisible to the outside world. E.g., suppose the task was to deliver a list of the people in a department with name, title, and salary, sorted by salary descending. All we really need to do is to populate the table in whatever way is convenient and have an index on the table by salary and the list pops into our hands without having to do any actual work. But, the list we hand back from the method is a list of only the information we need for the task, not a copy of the table or buffers or anything. Just the list. Once you start handing out buffers, you have violated the encapsulation.
 

progdev1

Member
best solution will depend on the context. For example, why is there a collection to start with? Are we doing something where we can't just process the employees individually? Is this group something like all the people in a department and we are doing something complicated with the set as a whole?

This kind of context is important in OOABL because we have a combination of it being relatively expensive to create objects, so creating lots of them we don't really need is contraindicated, and we have temp-tables and ProDataSets which are relational, not OO, but which are still very good ways to handle sets of data.

But, one of the key points of encapsulation is to put the operations on the set inside the class. I.e., we are not merely encapsulating the data, but also the operations. That way, the way the operation is fulfilled is invisible to the outside world. E.g., suppose the task was to deliver a list of the people in a department with name, title, and salary, sorted by salary descending. All we really need to do is to populate the table in whatever way is convenient and have an index on the table by salary and the list pops into our hands without having to do any actual work. But, the list we hand back from the method is a list of only the information we need for the task, not a copy of the table or buffers or anything. Just the list. Once you start handing out buffers, you have violated the encapsulation.
Okay sorry maybe I'm not understanding your point. From a context point of view I want to get a list of all Employees to be displayed in a Employee List Browse program at the front end. Then the user can click add delete or modify and it bring them into a employee maintenance program for add and modify or will just call the delete method on Employee.

I accept I don't need the entire table for the employee list program. I probably only need EmpNum, FirstName and Last Name to be displayed. I need to get each Employees': Employee Number, First Name and Last Name. I accept this is not the all the data in employee as there are other fields.

Before classes when I did this I had a temp table definition with the subset of data I needed and I populated it with a persistent procedure.
Code:
run persisitentproc.p persistent on AppServer SET hProc 
run GetGetEmployeeList in hProc (output temp-table tt-Employee).  
for each tt-Employee: 
    /* Display a list of Employees to the user in a maintenace Browse program*/ 
end.
...
/* In persistentproc.p */
procedure GetEmployeeList...:
for each employee no-lock:
    create tt-Employee.
    buffer-copy employee to tt-Employee.
end.
I am trying to do the same thing now, but use a class instead of a persistent procedure. I understand the an Employee Class is only for actions relating to an individual Employee and that makes sense. I understand that I need a wrapper EmployeeList Class to populate the temp table so as to keep the internal data private. What I don't understand how is how I get the subset of data from the database table to the temp table so I can write it back to the front-end via JSON but still have well formed OO code.I would assume the below is wrong but I am not sure how to do this correctly.

Code:
CLASS EmployeeList:
METHOD PUBLIC HANDLE GetEmployee( ):
    For each employee
       Create tt-Employee.
       BUFFER-COPY Employee TO tt-Employee.
    end.
END METHOD.
 

tamhas

ProgressTalk.com Sponsor
The problem here, I think, is taking an old existing structure and replacing one piece of it ... often, when one does that, the part that remains is not structured the way one would like for interfacing with the new component. Thinking in OERA layer terms, actually fetching the records you want to include is the job of a data access layer object. Personally, my inclination is to make any interfaces across layers as simple messages to avoid technology coupling, so that would point me at a JSON message containing the list.

Now, you have two choices. Either the DA layer object always returns full employee information or it has a mechanism for returning subsets. If it can return subsets, then the JSON message that DA sends to the business logic layer is already the message you want to send the UI, so the BL just needs to pass it through. If the DA is going to return full information, then you need a BL component to strip down to the essentials you want and then pass it through.

The only place in this structure that you need a TT is in the DA layer where you might have a PDS that you FILL to get the data. You could use a separate one to trim down the data if you wanted to do it that way instead of pure JSON.

But, I suspect little of that fits with your current structure. So, you either need to fudge something that fits the current structure or replace a piece with new structure and then change the interfaces to fit.
 
Top