I'll admit it -- getting acclimated to the world of objects is a shock. Not since the advent of structured programming has a solution been so highly touted as The Answer for the many problems of crafting software. With the advent of Visual FoxPro, I'm sure you're hearing nearly everywhere that Visual FoxPro has vaulted to the leading edge of application development toolsets.
Now don't get me wrong -- OOP is certainly new to even the most experienced and GENSCRNX fluent FoxPro developer. It is one of the most exciting new technologies to enter the sphere of application development since the PC.
Maybe you're also just a little fearful, as well. For this new FoxPro represents a radically different approach to building applications. It is without a doubt the largest leap from a prior version we've ever seen in Xbase. How are you going to absorb it all? Just how soon can you get productive with the new environment? What if you make a disastrous architectural mistake that forces you to rewrite your first big VFP project? Or worse, makes you say something at a user group or on the Foxforum that makes you appear naive? Being a little intimidated is certainly understandable.
This session is intended to help FoxPro developers new to the object oriented mindset make the transition to working with object most effectively. It explores detailed techniques as well as some of the "big picture" perspectives which I hope will help you get the most bang for your buck with object development.
To do this, this session focuses on a few basic principles:
OO truly does represent a radically new mindset and perspective on programming. What the marketing mavens of software haven't told you is that it won't be easy to grasp at first. By extolling OO's virtues as loudly and as often as possible (who can blame them -- their job is to sell the stuff, not use it) they unknowingly exacerbate our worst fear: "Everyone else thinks this OO stuff is easy -- but I just don't get it. I must be stupid."
So what do we do? Slink off to the bookstore to buy some instant wisdom on OO. What do we find there? Shelves loaded with titles on OO! And what wisdom do we gain when we sneak home like high-schoolers with our Cliff notes? Two things:
The terminology: Bullet definitions of a half dozen multi-syllabic Greek terms like polymorphism and encapsulation (no matter which book you read, it seems the definition was copied from some other book -they all sound the same, and it makes your eyes glaze over).
The example: "This is a circle. It's an object. Let's subclass it. Now it's a red circle. Let's do that that again. Now it's a red ellipse! Got it yet?!? See how easy this is?"
This drives me crazy. I'm a database programmer. I build information systems for a living. Customer records, inventory records, pick lists, calculations, reports. Exactly how is this red ellipse relevant to my problem?
In truth, I think OO will revolutionize the way we write software. Even more important, it should radically alter the way we live with what we've written. It should facilitate the way parts are assembled into solutions, and greatly reduce the amount of code we have to churn out to craft a solution to a problem.
But this change won't come easily to those of us who face the task of relearning nearly everything we know about procedural programming. It won't come overnight, and it won't come effortlessly, particularly if we're kidding ourselves about the fundamentals. How long did it take you to learn how to be a programmer in the first place? Did it dawn all at once, or was it a gradual process of "aha's" built one upon the other? Expect your personal Object Orientation to develop in that way -- gradually. The sooner we come to the resolution to proceed gently and patiently to explore this new world with fresh eyes, the sooner we can get down to business.
OOP is Good For You
One thing for certain is that a lot of really smart programmers have shown that object oriented technology is applicable to nearly all aspects of software development, and that it can result in radically better software, and a radically better process of developing it. Specific techniques vary considerably from environment to environment, and problem to problem. But if you insist in living in denial of the conclusion that this technology is very relevant to the process of improving your ability to construct software, you'd better consider membership in the Flat Earth Society.
On the other hand, just because something is OOPy doesn't make it smart, efficient, maintainable, or even understandable, for that matter. There's no reason you can't create a mess of object spaghetti code as opaque and brittle as the worst procedural code you ever had to maintain.
I remember interminable debates in the 80's about whether Xbase was "relational". If we distance ourselves from the ivory tower of theoretical purity and talk for a minute about the real world of solving information systems problems with real world tools, the Xbase data model allowed you to do most everything right in building a normalized dataset. It also allowed you to do nearly everything wrong. And it offered nothing to help you tell the difference between what was right and what was wrong.
OOP is Visual, but not all that Visible
History is repeating itself with the emergence of OO into Visual FoxPro. In fact, none of the popular new OO environments offer any guidance as to the right and wrong ways of developing effective software.
Additionally, you are already aware of the immense language bloat that was inflicted on FoxPro to stretch it so it could work with windows, host cross-platform development, and generate GUI. Now there's an absolutely huge new set of commands and keywords to support OOP. This means that there are even more ways than ever to approach a solution to a given problem in Visual FoxPro.
The challenge is to get to the heart of the new technology, and adopt a style that delivers the most functionality and the cleanest maintainability with the smallest amount of development effort. Pretty simple. So lets get into it.
Eliminating Redundancies of Logic
Redundancy is a huge cause of maintenance headaches. If you have only one instance of a piece of data, (a customer phone number, for example) there are only two states to worry about: either it's right or it's wrong. But if the number is stored in two places within the system, there are now at least two additional states to worry about: If the two numbers don't match, which one is right? As the number of copies of the data increases, the maintenance burden grows exponentially.
On the other side of the fence, non-redundancy gives you leverage -- when you change a product price in the product table, the new information should propagate to every screen, report, and calculation referencing that price. This is why data normalization delivers a benefit you can actually feel in your application work.
When working with data, we use tables, which are simple structures, to eliminate redundancy. But the liabilities of maintaining similar or near-similar logic is much worse than with data. Data is either right or wrong, but program logic has to be understood and tested, which can become darn near impossible when it gets complex.
To eliminate redundancy in program logic, we need a software framework that allows us to normalize program logic similar to the way we normalize data. Programs are more complex structures, and instead of tables we need a hierarchical set of blueprints to eliminate redundancy.
In the above schematic, we can place logic to enhance all dialogs in the most basic class, logic to enhance any one platform-specific case in the middle class, and logic dealing with one locale in the most specialized class.
Layering your application's functionality into a class hierarchy does for your code what data normalization does for your data. It allows you to reduce to a single instance each kernel of logic within your application. There's a single correct place to hook each piece of enhancement logic in a properly designed class hierarchy. (Just as there's a single correct place to insert a piece of data in a properly normalized database design.)
Just like data normalization, the process of class design may seem somewhat abstract from the problem at hand. But huge real-world benefits accrue to the project when your class design supports the specialization required by your application.
Is Inheritance Overused?
Inheritance is the most dramatic new feature of an object oriented environment, but it's not the only one by any means. Of equivalent impact and significance is the ability to construct complex objects by assembling other simpler objects inside containers. The creation of composite objects can be either visual, as in forms that contain grids, button groups, array properties, and so on, or non-visual. For instance, a "deal" may contain numerous Purchase Order objects, each of which contains numerous line items. Each Purchase Order may contain numerous Invoice objects, representing invoices which fulfill the purchase order, each of which contains numerous line items, and so forth.
When maintaining and enhancing an existing application, inheritance provides a remarkably useful mechanism for incrementally modifying the existing functionality at a very fine level of detail without while avoiding the risk of breaking the application in production.
When designing new systems from scratch, however, you have the option to create functionality by inheritance or by containership. There is no right answer for all scenarios. To come up with an optimal design for a given problem, you will have to look at the totality of the implementation and the downstream maintenance implications.
One piece of advice from one savvy group of students of OO suggests that use of inheritance hierarchies is often overdone, at the expense of more flexible downstream maintenance. There are several problems with long inheritance hierarchies, including:
Sluggish Performance. There's a lot of CPU cycles required to assemble the properties and methods required for a runtime instance of a heavily subclassed object. This computation is performed each time an object from the subclass is instantiated.
Obscuring the Clarity of Design. When you're looking at a class that is derived from a chain of parent classes more than five levels or so deep, it becomes really hard to tell what level a given behavior is controlled from. You spend an inordinate amount of time searching up the chain, with explicit "scope resolution operator"-adorned calls sprinkled around to keep things interesting. It an quickly look like spaghetti, even if it isn't.
Lack of Flexibility. As a class hierarchy becomes larger, its tougher to get the benefit of a piece of functionality embedded deep inside it. The more you think about it, the more your guts say "This isn't the right direction. There's got to be a simpler way."
In fact, assembling functionality by adding small, specialized objects to form composites that give powerful and flexible functionality appears to provide a superior result. Each time you spot a degree of flexibility that may broadly impact the application downstream, think about an intermediary object class which can elegantly encapsulate the functionality while limiting the impact on the rest of the code.
Adding one object into another isn't the only way to create functionality from small specialized classes. Runtime instances can simply cooperate with one another, and merely need to know about each other's existence in order to get some work done.
The conclusion here is to consider the design alternatives carefully. There are plenty, and the developer who forms a strong opinion too early may be his own worst enemy in achieving an optimal design to fit a given problem.
Tip: ADD OBJECT vs. CREATEOBJECT()
There are two different techniques for building composite objects. You can simply define a custom property, and assign an object handle to it using CREATEOBJECT(). Or, if the master object is a container, you can execute an ADDOBJECT() method at runtime or an ADD OBJECT command at design time. The difference is that when you use either ADD OBJECT or ADDOBJECT(), the object becomes a member of the container and can use the Parent property to refer to the immediate container object.
When using CREATEOBJECT on an instance variable of an object, that property is simply being used as a reference to the instance of another object. It does not "contain" the new object. Therefore the PARENT property does not point to the "would-be container", for in fact the object is not part of any container.
Either way, when you add or combine dissimilar objects together to create more specialized functionality, it is called composition.
NOTE: Objects can be assembled via composition either visually or programmatically.
The Name Game
With the addition of an Object model to Visual FoxPro, names have taken on substantial new importance. With all the discussion about fancy mechanisms like inheritance and encapsulation, one of the most profound contributions to better, more maintainable software is the way pieces of the system are named.
An object oriented environment provides an elegant and comprehensive system of sending a message from anywhere to anywhere else in the executing program. While the specific descriptors for a system event like a mouse click vary from vendor to vendor, the importance is the internal consistency that is imposed on all programmers working with a product. You don't have to memorize hundreds of variable names unique to an application to be a fluent OO developer. In fact, the need to rely on variables to reflect internal mechanisms is greatly reduced. This clarity does what naming standards never could -- providing a common language that spans all applications built in a product.
Xbase's traditional 10 character limit on field and variable names forced this problem to near comical extremes. How do you express, in a variable name, the background color of the column header of your scrollable customer list? SCRLHDBKCL? Now that "Hungarian" notation has become a popular de facto standard, your first two characters are supposed to be 'lc'. lcScHdBkCl?. Not good.
Well, you say, Visual FoxPro now supports 32 character variable names, so it's not a problem any more, right? We can call it lcScrollboxHeaderBackgdColor (with 3 characters to spare!). The underlying problem with this approach is that every attribute now has a single monolithic name which has to describe both the attribute and the identity of its container. In this example, there are two nested containers -- a header that is itself contained within a scrolling list. Since the name is arbitrarily made up, spelling it later will be a challenge. Was it '...Backgd...' or '...Bkground...', for example?
As a system grows richer and richer with details, you require more and more of these names, and simply learning and spelling them correctly becomes a burden. The Chinese language has a similar problem, with tens of thousands of primary glyphs, each with a separate symbol and meaning. As Greek and Latin demonstrate, breaking down concepts into words, and breaking down the words into a small set of reusable letters, is far simpler to learn than a compendium of thousands of distinct glyphs when each one has a distinct meaning. Computerization in China will be far more difficult for decades to come, due solely to the difficulties imposed by its complex language system.
By establishing common names for primary objects and their attributes, object orientation creates a 'lingua franca' -- a common component-based language for identifying things within the system, chopping down the need to learn hundreds of monolithic variable and procedure names to express behaviors in code. So in FoxPro, I can address the header color attribute as 'MyGrid.Column1.Header1.BackColor'. Since MyGrid, Column1, Header1, and BackColor occur consistently in the code to refer to the components they name (you can see them anytime listed in the comboboxes in the design tools), the mix-and-match addressing elegantly solves the problem of naming the many components of a rich environment in a consistent and learnable way. Any programmer needing to change this attribute will likely have already dealt with some other object's BackColor and some other attributes of MyGrid and its Column1 and Header1 components, and so will be able to re-assemble any desired name from its parts. In the same way you express a unique and precise phrase by choosing from known individual words, the object programmer addresses a unique system detail by stringing identifiers from a common list. If FoxPro is your first object-oriented environment the number of new terms may seem overwhelmingly large, but clearly there are far fewer property, event and method names than the total number of permutations of unique variable names a complex system would otherwise require.
More typing, but it's clearly worth it!
Short Cuts for Long Names
OK, so MyGrid.Column1.Header1.BkColor is precise, at least the first time you see it in a method, but its no great joy to type repetitively . The new object syntax gives you two powerful new mechanisms to reap the benefit of these explicit identifiers while avoiding repetitive typing.
1) Relative referencing
You may assign an object reference to a memory variable. That doesn't create a copy of the object, just another way of referring to it. Then, until the variable is released or loses scope, you may refer to it in your code instead of the long name.
x = MyGrid.Column1.Header1
x.bkcolor = RGB(128,0,0)
x.visible = .T.
release x && Releases the object reference, not the object
Note: if you try to release the object when any object references are lingering around, the object won't release until all the references are released too.
Relative referencing is particularly handy if you need to pass the object as a parameter to a method, or to another object's method.
This form works very well if you are assigning a series of properties
LongReference = createobject("myform")
wait window ;
"Now the window instance 'LongReference' is instantiated."
.left = 24
.width = 600
.top = 20
.height = 300
.backcolor = RGB(192,0,0)
.FillColor = RGB(0,0,128)
.FillStyle = 5
.box(10, 20, 80, 120) && Calling a method just for the fun
.FillStyle = 0
.FillColor = RGB(0,128,0)
.circle(100, 200, 50, .5)
.caption = "Boo!"
.ControlBox = .F.
.MaxButton = .F.
.MinButton = .F.
wait window ;
"I've changed 13 properties and called ;
2 methods without spelling LongReference."
An additional benefit of the With...Endwith construct is that it runs faster when compared with a stack of multi-element object references.
As your most important "black-box" functions mature, they require more and functionality to meet the range of exceptions you encounter in application requirements. To meet these requirements, you typically introduce additional CASE statements into the logic, and pass additional parameters into the functions. Sometimes for readability or to avoid writing redundant logic, you break out internal "slave" functions which are only valid within the context of the function that calls it.
In fact, FoxPro 2 provided a technique for calling a procedure one level down inside a procedure file. (DO MYPROC1 IN MYPROG). However, this technique was too limited to be of widespread use. Functions couldn't be similarly called, and the nesting only worked for a single level.
Some sharp FoxPro 2 developers discovered that they could structure programs with different procedures of the same name. They could use FoxPro's calling stack behavior to control which MYPROC1 would be called when a program issued a DO MYPROC1. However, the result was not obvious to someone reading the code later, and an intervening SET PROCEDURE TO or SET LIBRARY TO command that changed the search path could introduce ambiguities and errors.
In an object oriented environment, series of instructions (methods in OO parlance) that occur within an object can be given meaningful names without worrying about duplicating the concepts or managing a search path. In fact, this common naming is not just a tricky workaround, it is explicitly encouraged. An Invoice object and a Calendar object can both have a Print method. The object.method naming technique immediately removes the ambiguity, as in Invoice.Print() vs Calendar.Print(). Note how it's a lot more readable and easier to understand and express than INVOPRNT vs CLDRPRNT, etc. In an OO system you can reliably command any object to "print itself" by "sending the message" (OO speak for executing the command) <object>.Print(). In OO terminology, this wonderful virtue is called polymorphism. I'll spare you the Greek etymology, which will add little to your appreciation. But now that you can see how it will make your life better, it may be easier to add it to your vocabulary than by trying to memorize its definition.
In fact, some method names are common across all objects. An Init method can be used to modify properties of an object immediately after its creation, and before it is made visible for interactive use. A Destroy method is called when the object is being released. An Error method directs processing if an error occurs within the object's code at runtime. A Show method displays any visible object, and so forth.
Standard Property Event and Method Names: A Cure for dBloat
We've explored how object naming resolves two big difficulties in procedural Xbase: variable names and procedure/function names. But the naming system delivers an additional, unexpected benefit. It sets in place a linguistic standard that helps reverse the creeping problem of language bloat in Xbase.
In Visual FoxPro, the object naming strategy will (mercifully) obsolete much of the narrow, complex and ambiguous syntax of the FoxPro command language. One of VFP's base objects, the form object, offers all of the attributes of windows, plus many more. By exposing every individual attribute as a property, it provides a natural programmer's interface, allowing you to inquire and/or modify each attribute individually. Numerous FoxPro command and functions have been rendered obsolete, and you will find the new syntax much more natural to work with.
** Program PPEX01.prg ***
** Demonstrates how new object naming syntax
** in FoxPro reduces language bloat
** by providing a standard way to both
** read and modify object attributes
x = createobject("myform")
** Creates instance X of class myform,
** a subclass of form
** Draw the form
** Change the window title
x.caption = [My Revised Window Title]
** Required MODIFY WINDOW ... command in FoxPro 2.6
** Move the window 10 spaces to the right
x.left = x.left + 10
** Required ZOOM WINDOW ... plus a lot of
** WCOLS(), WLROW() etc. functions in FoxPro 2.6
** Turn off the Close option in the control menu
x.closable = .f.
** Required DEFINE WINDOW redefinition in FoxPro 2.6
define class myform as form
caption = "My Window Title"
left = 10
width = 70
top = 6
height = 15
closable = .t.
Caption: In an object-oriented environment, each object, like the form above, implicitly presents a full API -- a defined, consistent way of managing all its attributes.
While the obsoleted commands and functions will need to remain in the product for years to provide backward compatability, there is no reason for any of them to appear in your new code. A new public object _SCREEN allows you to create expressions like _screen.activeform.left, _screen.activeform.closable, etc., to address any of the rich assortment of form properties for the current form. Formerly you would have had to use the WOUTPUT() function to identify the active window, then manipulate its properties as much as the existing commands would allow. Also note that all visible objects have TOP's, LEFT's, HEIGHT's and WIDTH's. These keywords are reusable with every type of screen object and require no further explanation.
Sending a Message
If you've explored the Property Sheet and Method Editor in Visual FoxPro, you can already see that there are a rich variety of attributes and activities that FoxPro's base class objects can exhibit. Most of an object's properties can be modified at runtime by simply sending a message to the object assigning a new value to the property. The object's methods can be similarly be called to execute. For example, to get a combo box named 'MyCombobox' to be disabled, we simply have to issue the command MyCombobox.Enabled = .F.. Simply changing the enabled property causes the combo box object to handle the details of "dimming" its own visual image, and assuring that if subsequently clicked, it won't do anything. Sending a subsequent message MyCombobox.Enabled = .T. reenables the control to respond to a click.
A message that tells an object to "do something" is much the same as changing a property. We simply invoke the method name at the end of the object name. Example: MyCombobox.Refresh( ). The parentheses are optional unless parameters are going into the method or a value is to be returned.
However, there are several reasons why we might not address this message just to MyCombobox. Sometimes MyCombobox isn't even directly addressable. For example, if we are currently in a method belonging to another form entirely, we have to send the message to the form containing MyCombobox. (as in Myform.Mycombobox.Enabled = .F.) This may seem longwinded at first, but it makes it possible for objects of similar function on different forms to have the same name. For instance, it is an unnecessary chore to have to create 67 different and unique names for the Close button on each of your 67 different forms. Each can be named cmdClose, and messages will go to the correct object because messages to the exit button on any form can be addressed to <formName.cmdClose>. (In fact, the object model allows you to create a class cmdClose and create 67 separate instances which all derive from the same button. But let's not digress.)
If we're sending a message from a method in another object on the same form, we could refer to Myform.Mycombobox, but this hardwires the message to the form name. Instead, we can use the syntax Thisform.Mycombox. Visual FoxPro compiles THIS, THISFORM, and THISFORMSET into tokens which resolve to their actual object names at runtime. Although it compiles forms when saved, allowing it to check syntax on the spot, it defers the resolutions of THIS, THISFORM, and THISFORMSET, allowing you to write "generic" messages from within any object, as we'll see next.
(Visual FoxPro actually compiles forms when they are saved. This allows it to check syntax of methods for compile-time errors on the spot. At the same time, it swaps in the actual names for tokens THIS, THISFORM, and THISFORMSET.)
The Object-Oriented "Billiard Shot" (I)
You can change any object's property or trigger any of its methods from any other object. You just have to know how to assemble its full name, relative to your calling location.
The Object-Oriented "Billiard Shot" (II)
Same Principle, different addressing code depending on which container is the reference point for the message.
Object messaging is like dialing the phone:
It all depends where you're calling from.
|Message Sent From:||OO Message code||Phone System Analogy --
|How to dial|
|Another method of the same object||THIS.Enabled = .T.||To call another person in the same office||Dial 2 plus their extension.|
|The form that holds the object||THIS.MyObject.Enabled = .T.||To make a local call||Dial the 7-digit local number|
|Another object within the same form||THISFORM.MyObject.Enabled = .T.||Inter LATA local call||Dial 1 + 7 digit local number|
|From outside the form||MyForm.MyObject.Enabled = .T.||From outside the area code||1 + area code + 7 digit local number|
|If MyForm is in a Formset, from any object in another form in the same Formset||THISFORMSET.MyForm.MyObject.
|From outside the country||. 011+Country Code + Area Code + Number|
|If MyForm is in a Formset, from outside the Formset||MyFormSet.MyForm.MyObject.
Enabled = .T.
|From another planet||Better call the Operator|
Sending Messages through Containers
The relationship of containership is pretty much what you'd expect: It is possible for one object to contain another. Nothing is implied about the similarity of the objects contained. In fact, only certain objects may be contained by others. A formset can contain forms. A form cannot contain formsets. Similarly, a form may contain commandbuttons. Commandbuttons may not contain any other objects. This is all pretty much straightforward.
In the above examples, since the Formset contains the form, you may address an object within a form from the outside as FormSetName.FormName.ObjectName.Property = Value.
If you are in a method within a container, you don't need to know the name of the container in order to "bounce" a message off it to another object it contains. (See the OO Billiard shot sketch.) In fact, its far more generic if you don't name it explicitly. There are two techniques for referring to a contained object indirectly. One is to use the token Thisform or Thisformset. These are special keywords that are swapped at runtime for the actual container names. The other is the Parent property. Each object contains a reference to the container in which it was created. By evaluating the expression This.Parent, you get the name of the container. These can be chained together, as in This.Parent.Parent.
The only disadvantage of 'Parent' is that you need to know how many levels of containers to point through to get to the name of the one that contains your target object. Thisform goes right to the form level, regardless of levels of containership inside. (Remember the grid object holds columns, which may in turn hold other objects. Page frames may also hold other page frames. There are many possible combinations of objects within objects.)
Fancy ways to send messages
There are far fancier ways to send messages in OO environments. If your system is very complex with many interrelated moving parts, messaging takes on a whole new perspective. It may be the vehicle through which most of the application's functionality is delivered. You may want to create some more objects who specialize in "messengering" -- sending messages from one object to another. This technique may seem ponderous in simple examples, but has a huge payoff when used consistently in large applications -- it decouples the caller from the receiver, making possible cross-platform, cross-implementation structures which can easily be modified and maintained because all the application directives go through that single point. This technique is called "bridging" -- for more details seek out material on "Design Patterns in Object-Oriented Software" an emerging discipline. (Note: Steven Black is giving a session on this topic at this conference.)
Consider a form launcher. It might be called from
Alternatively, each of these objects could have code like DO FORM MYFORM... in its menu "ON ACTIVATE" code, click event, timer event, and so on. However, having this message flow through a single aperture within the application gives the developer lots more leverage to control the maximum number of instances and other cross-form logic. Here is where decoupling (the launching mechanism from the launched object, in this case) saves the day.
A Typical Application Hierarchy
So with all this talk, how does an applicaton hierarchy actually look? How many layers does it have?
The following is a reasonable place to start for basic database applications:
Scraping Base Objects off
of the Form Design Toolbar:
Subclassing all the base objects
Visual FoxPro comes with a couple dozen base classes from which all other objects are derived. You cannot modify VFP base classes, or modify the root object from which they are created. Therefore, to get global control of all commandbuttons, for instance, you must create a subclass from Commandbutton before you start building anything with buttons. Do the same with forms, and consider the same for each object you will use more than once as an application component. Put these in a VCX, and assign bitmaps so you can tell what they are from the form controls toolbar.
In this way, you have a basis to enforce global standards in button size, fonting, form artwork like logos, etc., from a single point of reference. It also allows for a measure of hindsight when dealing with down-the-road issues that threaten to hit every object on every form, like multiple platforms, internationalization, etc.
Caution: hierarchy overload possible
Just because inheritance is incredibly cool doesn't mean you have to use it to excess. In fact, it's possible to build components that are very awkward to make applications with. You might build a set of "Single Table Maintenance" buttons -- Add, Delete, Edit, Find, Next, Previous, etc. then you drop them on a form only to find out there's no Add needed in this one, no Find in the next one, etc. You can't drop a single object from a collection -- you have to hide it, then manage closing up the gap, and so forth. Also you can really feel performance bog down when you subclass a contained group of objects a couple of levels. VFP has to do all that searching for functionality up the class hierarchy at runtime.
Conclusion: Consider designs that assemble applications from simple class parts that work in close collaboration.
Types of Classes
The form design objects you get with Visual FoxPro provide a basic set of tools for building common interface components. However, there are several other types of classes you'll need to build from time to time, or encounter in other developers' VFP work.
Individual Interface Objects
As discussed above -- gives developers domain over all components in the application from a single point of control.
Groups of Interface Objects
Common assemblages can truly be written once, used many times
Advantages: If you add an additional control to a group, it propogates everywhere the group is used. (Presuming there's space for it to appear.)
Disadvantage: To suppress one of a group of controls, you have to hide it (VISIBLE = .F.) and deal with the vacant space (perhaps by moving the other objects around)
Special Process Classes
Handle "traffic direction" and "inventory control" within the app
Business Object Classes
Purchase Order vs Purchase Order Form
If there is substantial business processing in the application, or the possibility to connect to other applications in a workflow environment, there may be wisdom in separating certain business objects from their visual representation. In other words, a Purchase Order may in fact be a business entity distinct from a Purchase Order User Interface.
A PO object may well be of a lot of use in an information system without a form. It may need to know how to print itself, post itself, recalculate itself, validate itself, and so forth. Invoice objects may need to consult with it as part of the process of determining their own validity. It is possible that any of these processes may be executed while a PO has a visible representation in the environment, however it is equally easy to see how the PO object may be useful in processes where the visual representation (form) is not appropriate or desired.
In simple applications, this distinction may be academic, but in more ambitious business processing, it may become essential. In these cases, agglomerating all the navigation, data I/O, security, calculation, etc., into the form representing the business object may result in a confusing overload. For instance, why endure all the overhead of creating 100 forms when you want to batch up a group of PO's and route them for processing?
Respond intelligently to errors reported from various aspects of system
Designing for OO
There are two published techniques for developing designs for object oriented software: CRC cards and use cases. While books have been written about both of these methodologies, even a very basic overview can help you get started in leveraging the work of objects you build into your application.
CRC design follows a few basic steps:
Look at the design from a variety of angles. Examine and reexamine the relationships and responsibilities. Look for tangled logic and try to decompose it into a simpler construct. Don't worry about physical storage at this point, and resist the urge to mold the problem into an entity relationship sketch of the data storage model.
Use cases are hypothetical business situations against which the design is tested. Each use case serves as a scenario,against which the current version of the object design is applied. As application developers, we know that exceptions take up a disproportonate amount of the development effort in any project. Use cases are basically a technique to identify the exceptions that, if not handled, blow a hole in the design. One thing for sure: just as a developer is the worst person to test a "completed" application, developers will be unlikely to come up with use cases that break the current design model. Much more fruitful use cases will emerge from knowledgeable users with experience in the business rules and exceptions that arise in their daily work.
Taken together, CRC's and Use Cases represent a pair of approaches that remind us of the top-down vs bottom-up styles of classical analysis and design. The top-down approach reveals the structure of the major components of the design, while the bottom-up view serves as a cross-check that the design is sufficient to handle the exceptions. Trying both techniques alternately on a small project in a small team effort can be very illuminating.
Buy or Build?
Its appealingly easy to decide that, because there's no sense in reinventing the wheel, the no-brainer decision is to buy an application framework that will, no doubt, give you all the functionality you need and then some. That's not a bad idea, but there are some caveats to consider:
Prediction: Within about 12 months, VFP objects may more plentiful than pigeons.
Turns out that building a set of classes is the easy part. Documenting the library, maintaining it, teaching its proper use and reuse to a team, and fitting it to the problem your application is supposed to solve, and folding all that into a development strategy that actually achieves reuse are the really time-consuming parts. A class library understood by only a single developer won't achieve much benefit in the long run.
Predicament: How do you scale your project to someone else's foundation?
How big and complex is the type of application you're required to build? What type of application was the target for the foundation you wish to buy? How closely do these two match? How do you go about even learning how to answer the question?
One advantage to building a simple base foundation yourself is that you'll know how it works, not only in your head somewhere, but in your fingers. In other words, you won't be straining against what you already have when you go to implement it.
Advice: Buy functionality, not just architecture.
Scaling the Design to the Project at Hand
Know When to Say WHEN
When all the dust settles about our fascination with this new technology, the core issues will emerge. At the top of the list is the challenge of appropriate scaling to the application at hand. Because of its pervasive support for reusability, object architecture invites you to build tools. The Visual FoxPro environment goes one step further and invites the building of tools for building tools. This process can regress ad infinitum. Put another way, it's possible to spawn task after task of building classes and tools, while losing sight of the actual project that's paying your rent.
If you launch a nine month design process for a two week development effort, you're going to be in trouble. The same is true for two weeks of R&D for a nine month project. Many of us will be evangelizing the use of Visual FoxPro within organizations that perceive VFP to be a controversial choice, to say the least. What will be the evaluation of developer productivity if a project is overbuilt to ludicrous proportions?
This challenge is not going to be solved by a few "experts" espousing opinions about what architecture is "best" far removed from the context of the specific application you're building. Its going to be solved in the trenches by real-world developers doing what they do best applying the 80- 20 rule the common-sense point of view that says "do the 20% of the work that yields 80% of the benefit. Clearly you can run into trouble by over-design and over-tooling as much as cutting corners.
What kind of reality check can you provide to keep you focused on the most productive "sweet spot" ?
You know you need more design when...
Well conceived classes reduce the need to pass parameters to the barest few. Of course, when you drag and drop an object, its convenient and natural to receive the name of the "dropped" object. But the days of calling a black-box dialog box routine with umpty-jillion parameters (that only you can remember) are gone. Dependence on a long string of elements (some required, others optional) -- and the photographic memory required to implement them without having to go back to the programmer's reference -- is one of the main things that OO was intended to reduce.
If your objects have to "put out a lot of feelers" to know if its safe to execute their methods, something is not right. Most frequently, these probes take the form of TYPE("something") tests. If there's a lot of "object B can't exist without A, and C requires A and B", there's too much dependency built into the objects. Something in the relationship of the objects hasn't been thought through clearly. It doesn't matter that it works when run. There are a million ways to structure it so that it works -- for now. If it looks like tangled spaghetti, it probably is.
If an object has to reference its container, it can no longer exist independent of that container, and its potential for reuse is drastically reduced. If it has to reference out several levels, the web by which it is bound into that specific environment becomes thickly woven.
Its pretty common for a subclassed object to use the "scope resolution operator" (the :: in code) to call specifically up the inheritance hierarchy in cases where a method is overridden, but you need to enhance the existing method with custom code either before or after the parent class logic. While this call can be made dynamic with the use of EVAL(this.parentclass + [::methodname()] ), this misses the point.
The likelihood of replacing the name of the parent class with another is rather low, so the protection afforded by the EVAL( ) is fairly minimal. The problem is if there's lots of this kind of referencing going on in a hierarchy several levels deep, it becomes harder and harder to understand what is actually going on. This means that maintenance will be more of a "trial-and-errors" affair, and the class architecture will be hard to learn to use effectively.
Taking a monolithic block of impenetrably complex code and putting it into a one-method object technically qualifies it as an object, but its just an overgrown procedure in a disguise. While OO gives you the structural tools to break your code into simple modular components, there's nothing to insist you have to do so. An ugly method is every bit as intractable as an ugly procedure. There's nothing magical about calling it a method.
This is a dead giveaway that you need to move some functionality up the class hierarchy. The whole point of a good OO design is to give you a single place in the logical hierarchy to place each piece of logic and functionality. If you're copying and pasting, you're either using VB, or you're using Visual FoxPro incorrectly.
What's wrong with copy and paste? Rookies can be forgiven for falling into this trap, but not veterans. Copy and paste duplicates logic the same way de-normalized data design permits duplication of data. The difference is that duplicate logic is even worse to maintain than duplicate data. Logic flaws that are copied around are likely to wind up in more places than you can keep track of. The same with enhancements. You won't know just how many pigeon holes you have to go looking through when you realize the need to improve something.
Once this process has gotten really out of hand, the copied and pasted code isn't always the same from place to place. Mechanically retrofitting a fix or improvement into all the instances won't have the same effect in each case. You'll be well on your way to spaghetti-land and the need for the next programmer who picks up where you left off to issue that horrible verdict, "I'm sorry, but that will have to be totally rewritten."
You know you're overdesigning when ...
You've got an inheritance hierarchy running more than five to seven levels deep.
You've got intermediate subclasses that hold just a few lines of code or define one or two custom properties.
You're creating subclasses to allow for future capabilities that are totally out of the realm of reality.
A person you can perform a "reality check" with is a key resource
The cure? As a reality check, try a walk through of your design with a sympathetic but relatively non-technical manager or power user. Try to explain the justification for each class, and key methods. The litmus test is not solely whether you convince them that your opinion is right. If you can listen to your own arguments as you elucidate them, and you've got the guts to admit it, it's equally likely that you'll decide that your own reasons for or against a specific design idea require revision.
Don't solve a problem you don't have
The power and neatness of the tools invite you to tinker with "cool stuff" all along the way. To be of the highest service to your customers and the system's ultimate users, try to focus your most intense development effort on the core business issues.
Don't kill the project building the tools -- Abstraction can be an infinite regress -- The importance of the 80-20 rule
It's imperative to focus your development effort on the area where you'll get the most bang for the buck. If your application contains 40 master-detail forms, it would be best to delve most deeply into class construction in that area. Leave the 256 color animated therm-bar for another project.
Object technology opens up dozens of new ways of constructing applications. Where a problem may have prompted a handful of procedural solutions, there will be dozens of O-O approaches. O-O technology allows us to abstract problems to a much higher level. When this serves the needs of application development, O-O can be the force that powers far more sophisticated software development, and far more maintainable outcomes than we ever dreamed was possible in procedural code.
O-O is also much less language dependent than procedural languages. We observed how FoxPro 2.x development (with GENSCRNX, drivers, etc.) moved farther and farther from a common "xbase language fluency" into a kind of specialized expertise that couldnt be shared outside the circle of "Fox-heads". On the other hand, O-O concepts map from one implementation to another in a very straightforward way. Classes and instances, and the concepts that organize them, can be moved from one language/platform to another much more easily. Look for great design ideas to appear from many directions.
The distractions will also be enormous. The challenge to scale design to the problem at hand will be ever-present. It appears that O-O design, if complete enough, essentially becomes 80% of the development effort. However, theres the risk that once coding starts, the other 80% of the job will inexplicably appear. The absence of a clear set of guidelines and professionally accepted methodologies for pursuing OO design and development invites the possibility of projects that are designed interminably, and killed before they are ever implemented.
Were all going to be learning a lot over the next year or so.