Posted 18 years ago on 2/17/2006 and updated 10/26/2008
Take Away: Compromise OO implementation strategies for Paradox developers.
KB100308
Abstract: Compromise Implementation strategies.
Although not as popular as it once was, Paradox is still one of the best desktop database applications around. Many developers implement solutions using Paradox and ObjectPAL. In Paradox, the developer usually takes an object-based approach where the developer attaches code to the events of objects. The code attached is a form of procedural programming that uses the built-in objects provided by ObjectPAL. You use objects but you don't design and create them. When the need arises to reuse code, you put the common code in an ObjectPAL library or libraries. A popular approch to coding is Object Oriented Programming (OOP) which is part of an overall object oriented analsys, design, and programming approach. Paradox is not fully object oriented (OO), it is object based. A good tool doesn't have to be object oriented in order to be useful. However, if you develop database applications using object oriented analysis (OOA) and object oriented design (OOD) it is critical to understand how OO concepts relate to Paradox and to explore possible compromise implementation strategies. The purpose of this article is to introduce OO concepts to the Paradox community and to discuss techniques for implementing compromise implementation strategies.
Use an Object-Oriented Methodology
Object-oriented programming involves compartmentalizing and reusing code. Your goal should be to avoid duplicating code by developing self-contained, reusable units. After a while, you will spend the majority of your developing time copying, pasting, and putting together previously developed units. When a bug in a certain unit turns up, you can debug that one unit, and every application from that point on that uses that unit is cleaned up or enhanced. In addition, when you enhance some code that many applications share, those applications are enhanced instantly.
Keep in mind that you can still write spaghetti code in ObjectPAL. If you duplicate parts, you inevitably introduce bugs into your application. ObjectPAL, however, promotes good programming. If you follow the rules of object-oriented methodology, develop in compartments, and avoid duplicating code, your programs will be clean, less buggy, and more maintainable.
What is Object Orientation (OO)?
OO was born out of the need to create better reusable code. Typical non-OO applications end up with a library of code that is not very well organized (if at all). For example, in Paradox you store your reusable code in libraries. During the coding of your application, when you find a custom method or custom procedure that you will call from two locations, you put the routine in a library. Perhaps you'll name the library the same name as the application or simply utils.lsl or misc.lsl. By the time you're done coding your application, you might have several libraries of reusable code. Coding on the fly like this is useful, but if you knew prior to coding what routines would be reusable, you could organize your code better. OO allows you to do this and much more.
What does it mean to be fully object oriented?
Sometimes developers talk about degrees of object orientation. For example, Visual Basic is object based just like Paradox. With version 6, Microsoft introduced a form of abstract single level inheritance. Many Visual Basic developers thought Visual Basic 6 was fully object oriented. Those that knew better knew that Visual Basic was one step closer, but still wasn't fully OO. When using object oriented analysis and design, Visual Basic developers have to compromise on the implementation (just like Paradox developers have to).
Note: All Visual Studio.Net languages including the VB.Net language fully supports true full inheritance.
Classes and Objects
A class is a set of similar objects and an object is an instance of a class. You can think of a class as the design of an object that will be created in your code. A helpful analogy to draw upon is the architectural drawings of a building and the building itself. You can think of the architectural drawing as the class and the building that is built as the instance of the object. Here are some helpful characteristics of classes and objects:
Both classes and objects have identity. For example, you can have a MyCustomer object that is instantiated from a Customer class.
A class defines attributes and methods. An object uses these attributes and methods.
Because an object has the ability to set the attributes, objects are said to have state. The current state of an object is simply the current values of its attributes. Classes do not have state.
Each type in ObjectPAL and each tool on the Design toolbar is a class with attributes and methods. When you place a tool (for example a button tool) on a form, you create a button object from a button class. The class is the code that was instantiated to generate an instance of the button object.
Class Creation in ObjectPAL
Unfortunately you can't create classes in your code, but you can simulate classes using libraries. In Paradox, create one ObjectPAL library per design class.
Implementing methods and attributes:
Class Methods - For the sake of this document, you can think of custom methods and custom procedures as methods.
Class Attributes - In Paradox, you don't have true class attributes because you can't create a true class. The best you can do is to simulate an attribute with custom methods that set and return the value stored in a defined variable.
In object orientation you can declare a new class from an existing class. The new descendant class (also called a subclass or child class) has all the attributes and methods of the existing class (also known as its parent or ancestor). The new descendant class can have additional attributes and methods not found in the parent class and can even override attributes and methods of the parent. There are two common variations of inheritance: single inheritance and multiple inheritance.
Single inheritance is where your new class descends from only one parent class.
Multiple inheritance is a special type of inheritance supported by fewer languages (C++ supports multiple inheritance) where the descendant class is declared from two parent classes. The new descendant class inherits all the attributes and methods of both parents. The problem with multiple inheritance is that when both parents have attributes or methods in common (or in conflict), the descendant class must resolve which attribute or method to call. Multiple inheritance can be problematic from a design point of view. For example, if you try to design a new class that descends from both a button and field class you'll run into problems resolving whether the new object is a field or a button. Some true object oriented development environments, for example Delphi, Java, C#, and VB.Net do not allow multiple inheritance for this reason.
What about Paradox? Paradox doesn't support any form of inheritance and there is no easy way to simulate inheritance in ObjectPAL. You should therefore think in a class-based model that does not support inheritance (VB Classic, ASP Classic, and JavaScript are examples of languages that don't truly support inheritance but do support class creation). The best you can do in ObjectPAL is to create one library per class and use a strict naming convention to implement your class hierarchy design. For example, given the following class diagram:
You could create a library for each class and use a naming convention to emulate a relationship between the libraries. For example:
Firm.lsl
PrestwoodFirm.lsl
Client.lsl
HahnemannClient.lsl
App.lsl
AsbestosApp.lsl
Because of the lack of inheritance, notice there wasn't an easy way to implement BaseClass nor BusinessObject.
Encapsulation
Encapsulation allows you to hide the implementation details from the user of your code. The attributes and operations of your new class are known as the classes objects and each object (attribute or operation) has a predefined scope. At a minimum, these objects of your new class can have private, protected, or public visibility. Private visibility means that only the class itself can see the private objects. Private attributes and methods are hidden from all outside code. Protected visibility means that only descendant classes can see the protected objects. Classes that do not directly inherit from the class cannot see protected objects. Public visibility means that any code that has access to the class can see and use public attributes and methods.
Paradox uses encapsulation itself quite extensively. As a user of the TCursor type you can use any procedure or method defined, but you can't get access to the code within the TCursor type. You know what TCursor.open() does but you don't know specifically how it does it (nor do you care as long as it works correctly).
In addition, encapsulation is supported in ObjectPAL. Do you know the difference between a custom method and a custom procedure? Custom procedures are private to the form, library, or script they are in. Only code that is within the same object can see custom procedures. This is encapsulation. Custom methods are public and any code that can see the form or library can call the custom method.
What about protected visibility? Since ObjectPAL doesn't support inheritance and protected visibility is closely linked to inheritance, there is no good way to implement protected attributes and operations in ObjectPAL.
Polymorphism
Polymorphism allows any descendant class to redefine any method inherited from its parent class and to overload the method parameters passed in. This allows the software architect to design a dog.run and a cat.run methods and then the programmer can decide at development time whether to instantiate a dog or cat object and either way he knows to call the run method to make the object run. If the designer created individual MakeDogRun and MakeCatRun methods, then the programmer would have a hard time remembering both commands. In ObjectPAL, this type of polymorphism is used very well. For example, you have Form.open(), Library.open(), etc.
Although Paradox uses polymorphism extensively in both the GUI and in ObjectPAL, polymorphism isn't directly supported in the code you create since Paradox doesn't allow class creation and inheritance. However, you can simulate polymorphism using libraries. To simulate polymorphism in ObjectPAL, consider the library name as part of the custom method name. In non-oo Paradox programming, if you need a custom method that returns an employee name and a custom method that returns a customer name, you might create the following custom methods:
libUtils.GetCustomerName() ;Returns the name of a customer.
libUtils.GetEmployeeName() ;Returns the name of an employee.
During true OO analysis and design, you might create a customer and an employee class both with a Name attribute. You can simulate the implementation of these classes in ObjectPAL by using one library per class each class with a Name custom method. In your code you distinguish between the two custom methods using dot notation. The following code fragment demonstrates this concept:
Var
libEmployee Library
libCustomer Library
endVar
libEmployee.open("employee")
libCustomer.open("customer")
libEmployee.Name() ; Returns the name of an employee.
libCustomer.Name() ; Returns the name of a customer.
Using separate libraries with the same named custom methods allows you to use dot notation as above to implement a simple form of polymorphism.
Polymorphism and Substitutability
Substitutability allows a descendant class to be used anywhere an associated parent class is used. In object oriented programming a variable could refer to one object at one time and then another object another time. This allows the designer of software to create both a dog.run and a cat.run methods and then decide at runtime whether the variable will be a dog or a cat. I can find no good way to implement substitutability in ObjectPAL. My advice for Paradox programmers is to avoid this concept in your design.
Abstract Class
An abstract class is a class that cannot be instantiated. The intention of an abstract class is that a sub-class will implement the methods and attributes defined in the abstract class. In this way, all descendant classes of an abstract class will have the same or similar interface. I can't think of a good way to simulate abstract classes in ObjectPAL sufficiently to warrant publishing. My advice is to avoid abstract classes in your implementation of your design. In other words, design using abstract classes but only implement the descendant classes (which by definition must be fully implemented in your design).
Example: Using Business Objects
The following simple example will demonstrate converting a typical pushButton-level routine into using a business object (a pseudo class). You'll notice that it takes a little more effort to code this way, but the benefits of reusing the code later warrant the extra time spent now. The following code displays the number of orders a customer has placed. This might be useful, for example, if your client has a business rule that specifies the customer gets a discount on every 10 orders.
The following code on the pushButton event of a button accomplishes the task in a traditional technique.
1: ; Button :: pushButton
2: method pushButton(var eventInfo Event)
3: const
4: TBLCUSTOMER = "Customer"
5: endConst
6: var
7: tcCustomer TCursor
8: dynCustomer DynArray[] AnyType
9: liOrders LongInt
10: nRemainder Number
11: endVar
12: dynCustomer["Customer No"] = "1231"
13: tcCustomer.open(TBLCUSTOMER)
14: if not tcCustomer.setGenFilter(dynCustomer) then errorShow() endIf
15: liOrders = tcCustomer.cCount("Customer No")
16: nRemainder = liOrders.mod(10)
17: if nRemainder = 0 then
18: msgInfo("Customer Discount", "Give customer a 10% discount.")
19: else
20: msgInfo("Customer Discount",
21: "No discount this time. Customer has " + String(SmallInt(10 -
nRemainder)) + " orders to go
before they get the discount.")
22: endIf
23: endMethod
Taking the code above and translating it into using a business object requires creating and then using a library. The following is the translated business object (in the form of a library), starting with the Var window of the library:
1: ;Library :: Var
2: Var
3: tcCustomer TCursor
4: endVar
The following is the Const window of the library:
1: ;Library :: Const
2: Const
3: TBLCUSTOMER = "Customer"
4: endConst
The following is the open event of the library:
1: ;Library :: open
2: method open(var eventInfo Event)
3: tcCustomer.open(TBLCUSTOMER)
4: endMethod
The following is the first of two custom methods for this new business object. This custom method gathers the total orders for a customer. Since this part of the code was a good candidate for reuse, it was made into its own custom method.
Once you have created the above library, you can then use it. The following is the code that uses the business object starting with the Uses window of the form:
1: ;Form :: Uses
2: Uses ObjectPAL
3: "CustomerObject.lsl" ;Customer business object.
4: endUses
The following is the Var window of the form. It's a good idea to declare library variables at the form level.
1: ;Form :: Var
2: Var
3: libCustomerObject Library
4: endVar
The following is the init event of the form. It's a good idea to open all global libraries in the form's init event.
The following is the actual code that uses the library. It is in the button's pushButton event.
1: ;Button :: pushButton
2: method pushButton(var eventInfo Event)
3: var
4: sCustomerNumber String
5: endVar
6: sCustomerNumber = "1231"
7: if libCustomerObject.cmApplyDiscount(sCustomerNumber) then
8: msgInfo("Customer Discount", "Give customer a 10% discount.")
9: else
10: msgInfo("Customer Discount",
11: "No discount this time. Customer has " + String(10 "
libCustomerObject.cmGetTotalOrders(sCustomerNumber)) + " total orders.")
12: endIf
13: endMethod
As you study the technique of using business objects more, you might be tempted to put all the functionality of retrieving the data from a table into the appropriate business object and use the business object exclusively. I don't recommend this technique. Encapsulating all the functionality of retrieving data from a table into the business object would require you to abandon the use of a form's data model. To me, that is too big a sacrifice. The data model is a powerful tool and should be used. Use business objects in conjunction with the data model. In other words, for displaying data on a form, use a data model with defined fields and table frames. When you want to use a TCursor, open a table, find a value, and use it. That is a routine that is a prime target to move to a business object.
Once you become accustomed to thinking in terms of business objects, you'll find this technique quite natural. When you need to accomplish a task (add a routine to your application), you'll find yourself stepping back and thinking, "What business object does this routine belong to?" As your application grows, you'll find that you can reuse your code more efficiently than ever. As you learn more about object oriented analysis and design, you'll learn how to design business objects and other classes prior to starting to code.
Conclusion
In this article you learned how to implement object oriented analysis and design in Paradox. Although the UML has a plethora of diagrams including Use Case, Object Interaction, Trace Diagrams and many others, I encourage you to learn how to read and create the following two OO design documents:
Class diagrams - documents class hierarchy
Class Interaction Diagrams - documents how classes interrelate to each other
In particular, study how to implement classes in a non-oo tool like Paradox. You can use any of the popular OO methodologies to gather requirements and design your application and then use the techniques in this article to implement your Paradox application.
Abstract: Compromise Implementation strategies.
Although not as popular as it once was, Paradox is still one of the best desktop database applications for Windows and Linux. Many developers implement solutions using Paradox and ObjectPAL. The standard for building quality solutions is object oriented analysis, design, and programming. Paradox is not fully object oriented (OO), it is object based. A good tool doesn't have to be object oriented in order to be useful. However, if you develop database applications using object oriented analysis (OOA) and object oriented design (OOD) it is critical to understand how OO concepts relate to Paradox and to explore possible compromise implementation strategies. The purpose of this article is to introduce OO concepts to the Paradox community and to discuss techniques for implementing compromise implementation strategies.
What is Object Orientation (OO)?
OO was born out of the need to create better reusable code. Typical non-OO applications end up with a library of code that is not very well organized (if at all). For example, in Paradox you store your reusable code in libraries. During the coding of your application, when you find a custom method or custom procedure that you will call from two locations, you put the routine in a library. Perhaps you'll name the library the same name as the application or simply utils.lsl or misc.lsl. By the time you're done coding your application, you might have several libraries of reusable code. Coding on the fly like this is useful, but if you knew prior to coding what routines would be reusable, you could organize your code better. OO allows you to do this and much more.
What does it mean to be fully object oriented?
Sometimes developers talk about degrees of object orientation. For example, Visual Basic is object based just like Paradox. With version 6, Microsoft introduced a form of abstract single level inheritance. Many Visual Basic developers thought Visual Basic 6 was fully object oriented. Those that knew better knew that Visual Basic was one step closer, but still wasn't fully OO. When using object oriented analysis and design, Visual Basic developers have to compromise on the implementation (just like Paradox developers have to).
Note, Visual Basic.NET beta 2 does support true full inheritance.
Classes and Objects
A class is a set of similar objects and an object is an instance of a class. You can think of a class as the design of an object that will be created in your code. A helpful analogy to draw upon is the architectural drawings of a building and the building itself. You can think of the architectural drawing as the class and the building that is built as the instance of the object. Here are some helpful characteristics of classes and objects:
· Both classes and objects have identity. For example, you can have a MyCustomer object that is instantiated from a Customer class.
· A class defines attributes and methods. An object uses these attributes and methods.
· Because an object has the ability to set the attributes, objects are said to have state. The current state of an object is simply the current values of its attributes. Classes do not have state.
Each type in ObjectPAL and each tool on the Design toolbar is a class with attributes and methods. When you place a tool (for example a button tool) on a form, you create a button object from a button class. The class is the code that was instantiated to generate an instance of the button object.
Class Creation in ObjectPAL
Unfortunately you can't create classes in your code, but you can simulate classes using libraries. In Paradox, create one ObjectPAL library per design class.
Implementing methods and attributes:
Class Methods - For the sake of this document, you can think of custom methods and custom procedures as methods.
Class Attributes - In Paradox, you don't have true class attributes because you can't create a true class. The best you can do is to simulate an attribute with custom methods that set and return the value stored in a defined variable.
In object orientation you can declare a new class from an existing class. The new descendant class (also called a subclass or child class) has all the attributes and methods of...
Today I added a bit to the introduction in this article, cleaned up some formatting, and moved it to the Wicked Coding KB topic. This article is really for very advanced Paradox developers.
I feel the example based on interrogating the 'Customer' table to find out how many orders have been placed somewhat dubious in terms of database design - surely you would only have one record per customer in a Customer table and orders would be in an Orders table?
As far as OO design goes would a method to find out how many orders have been placed by a specific Customer belong in the Customer library (class) or in an Orders library? Or would you have a method in Customer.lib which called a method in Orders.lib?
>>...customer in a Customer table and orders would be in an Orders table?
Yes customers in customer table and orders in the orders table as usual.
>>...how many orders have been placed by a specific Customer belong in the Customer library (class) or in an Orders library?
Since Number of Orders is a property of a customer, I would put that property in the Customer class but you are correct that that custom method would indeed count the values from the orders table. It uses the orders.db table. Although classes frequently map 1 to 1 to tables in a database, when creating reusable code, you need to switch your thinking. You need to switch from a data-centric view to an object-centric view.
>>Or would you have a method in Customer.lib which called a method in Orders.lib?
If I wanted the ability to retrieve the number of orders of a customer in the Orders class, sure, you could design it that way. And yes, you could use aggregation where the Customer class "has a" orders object in it. In ObjectPAL, you would have to simply make use of an Orders.lib custom method.