This paper provides some preliminary information regarding the presentation. The example code is provided on the conference CD with this document and is not reprinted here.
Visual FoxPro is Object Oriented. So what does that to me as a developer? How can object orientation make my life easier? We all know there is a learning curve to climb in order to understand objects and we’ve been told there is a payoff for climbing that curve. This session will investigate one of those payoffs, a reduction in our development time.
Development time is always at a premium and steps we can take that reduce it are quite valuable. Object orientation promises us that our work will be reusable and that each project we develop will take less time than the previous one did. How do we cash in on this promise?
As is the case in all situations, the solution to a problem is never found until the problem is clearly defined and understood. Placing development time under a microscope shows us that it is divided into a number of different tasks. The major ones being analysis, design, implementation, testing, and user training. What are these tasks and how do they influence the overall development time?
All application development starts with the analysis of the business problem to be solved. A thorough analysis is instrumental to a successful solution. We are told that in object oriented development the analysis phase will occupy a larger percent of the total development time than in the structured programming practices of the past. What can we do to reduce the time required for this phase of a project?
Looking for patterns in business problems will greatly reduce the time for analysis. Cataloging these patterns as we identify them will allow us to spot them more easily in the future. With a catalog of patterns at our fingertips we can quickly recognize similarities to previous business problems and rapidly record the requirements.
There is a danger in any attempt to reduce analysis time, which is producing an incomplete analysis. When we cut corners on the analysis of a business problem we open the overall project to a number of time-consuming problems. It is possible that the missing requirements are not discovered until the project is well on its way to completion and that those undiscovered requirements force us to abandon a large body of work due to changes in the design.
If the business problem is not clearly understood by the designers and programmers of an application it is not only possible but also probable that the application will not meet the user’s needs. Applications that do not meet the requirements of the users are either abandoned or rewritten. It is scary how true the old cliché is; „There is never enough time to do it right, but always enough time to do it over“.
Outside of recognizing, using, and cataloging the patterns seen during analysis, this is one of the two areas of the development process that should not be abbreviated.
Design is the abstraction of a solution, in other words it is the general description of the solution to a problem without the details. Just as business problems can be categorized into patterns that repeat themselves, designs also exhibit patterns. Patterns seen in the analysis phase can be mapped to patterns in the design. By cataloging and reusing design patterns we can reduce the time required to create the solution design.
The design phase is the second development step that should not be abbreviated. It is surprising how easily and quickly goals can be accomplished when they are clearly defined. The design phase is the planning of the solution, without a plan an awfully large number of dead ends can be pursued. These dead ends will consume a great deal more time than development of a proper design would ever take.
Compare building an application to a family vacation. If , at vacation time, you packed your family in the car to drive cross country and you had no maps and route planned, what do you think the odds are that you will reach your destination? How many wrong turns could you take on the trip? How long would it take you to get to the other side of the country? Well building an application without a good design is no different.
Implementation is the execution of the design plan. This is where we actually write the code that does the work. This session focuses techniques to reduce the time of the implementation phase of development.
Starting with a sound application framework can greatly reduce the coding of the application by removing the need to deal with the housekeeping activities of the component interactions.
Reusing previously successful code has been a goal of programmers for as long as I can remember. Object oriented development promises again to allow the reuse of our code, although object orientation allows us to reuse more than mere code. We can reuse actual things that do stuff, widgets. These widgets can be as simple as a textbox that handles date entries, or as complex as a form that handles data management. An application framework is a very complex reusable widget comprised of many objects each providing services to the application.
You can recognize useful widgets during the development of a project. Once you find yourself doing the same thing a second time, stop and ask if this could be handled by a reusable widget.
Testing can be spread out throughout a project. We test things as we build them and then again when the project is complete to be sure they work well together. When we are reusing previously tested widgets, we save some time because these reusable items have already been tested individually so we are only left to test them in concert with one and other.
User training is one of the most often overlooked aspects and cost centers of application development. A shiny new business application does no good for the operation until the users are able to make it work. By capitalizing on reusable components we promote a consistent interface for our applications. This, in turn, reduces the training time required for users to be comfortable with the new system because they are already familiar with the user interface.
Object orientated system development has a set of principles and rules that prescribe the way things should be done. It is heard all to often that the theory is nice but pragmatism gets the job done. This is an unfortunate attitude for software developers using object-oriented tools. The principles of object orientation were not founded in some back room of a university, they were discovered during actual project development through the analysis of things that failed to work well.
These principles and rules are guidelines to success and they need to respected and understood. Following the rules protects us from making the same mistakes others have made before. It is true that any rule may be broken, however it si also true that before breaking a rule one should;
If you know all of these things and still choose to break the rule, go ahead and break it. What does all this mean? It means that it is necessary to know what the rules and principles of object-oriented development are. It means that the „theory“ is not dispensable.
The following sections discuss some of these principles and examine, through examples, how they promote reusability and reductions in development time.
The sections in Italics are references to the example classes that are included with the presentation and point out an application of the principle being discussed.
Delegation is the process of one object passing responsibility for an action on to a different object. This is usually done because of the natural responsibilities of the two objects in question. For example, consider a command button that is responsible for exiting a form. The responsibility of the button is to react to the user’s click action, but is it the button that has the responsibility of closing down the form? Perhaps the form itself is better equipped to deal with its own shutdown action. Through delegation we can have the button pass the responsibility for the form’s shutdown on to the form.
The click event code of the button delegates to the form by a THISFORM.Release() call thus removing responsibility from the button for dealing with the details of releasing the form.
With proper use of delegation we will break up the functionality of a particular operation and assign responsibility to the correct objects and methods. This provides the ability to execute pieces of that functionality independently which in turn reduces the requirement for monolithic procedures (methods) which are essentially huge DO CASE constructs reacting to the parameters passed to the routine. These monolithic procedures are a major nuisance during the maintenance phase of a project.
Defined as allowing a class to incorporate all or part of another class in its own definition, inheritance is probably the single most well understood property of object oriented development and, at the same time, the most overused. Inheritance is the process by which a subclass gets its behaviors and data from its ancestor classes. The idea is that the further down the inheritance tree we go the more specialized the class definition becomes.
While inheritance is a powerful facility it is also a limiting one. Whenever we try to modify a class in the hierarchy we need to determine the affect that the change will have on all of the descendent classes in order to prevent the introduction of problems. When an inheritance tree grows very large the ability to reuse any of the individual classes within that tree becomes more difficult.
Inheritance can be used to provide for polymorphism though. By defining the public properties and methods of an entire class of objects at the top of the inheritance tree we can standardize the programmers interface and provide for interchangeable classes.
Polymorphism: The ability of an operation to apply to several types. There are two types of polymorphism:
Inherent: Operations are inherited by every subclass.
Ad hoc: Different operations of different types have the same name.
By defining an abstract class that introduces all of the public methods and properties of an entire class tree we can provide for a consistent interface and the interchangeability of the various subclasses. In other words, if all textbox classes have the same set of public properties and methods they are interchangeable. We can easily replace one type of textbox with another without concern for some other object referring to a property or method that the new textbox doesn’t have. This is inherent polymorphism.
txtBase defines the public interface for all textboxes. txtCalculator and txtDate examples both inherit from the txtBase class thus providing a standard programmers interface for those textboxes and allowing them to be interchanged without requiring alterations of any code outside of the textboxes.
Ad hoc polymorphism can be seen in the cmdExit button class. Under Delegation above we mentioned that the button passes the responsibility on to the form for the release action. This is accomplished by the click code of the button calling the Release method of the form. Different forms may take different actions in their respective Release methods, meaning that the code in the exit button’s click is taking advantage of ad hoc polymorphism to provide reusability. The cmdExit button can be dropped on any form and it will work effectively.
Encapsulation can be defined as hiding the implementation of an object. When a class contains all of the data and code required for it to function without the need to depend on any outside objects at runtime, it is encapsulated. This encapsulation allows the class to be used without regard to the environment in which it is placed. The class does not require any specifics of its environment and it does not create any side effects on the environment. Encapsulation is very effective at making classes reusable.
All the needed properties and methods are defined as part of the class making the class independent of any sibling objects at runtime. This allows the cntProgress class to be placed in virtually any form without regard to what else may or may not be in that form.
Independence is a step towards achieving encapsulation. Making a class independent requires that any references to other object’s properties or methods be verified before the reference is made. This is sometimes called „defensive programming“. We can make a class independent by not assuming anything about the environment in which that class may find itself. Not assuming means that we check what for the existence of whatever we need before we try to use it.
The timer event checks for the existence of a TimedOut property in its containing form before trying to reference it. This allows the timer to be placed in a form regardless to whether or not that form has a TimedOut property.
Various base classes in Visual FoxPro have collections of their member objects, among these are the Form, Container, Grid, PageFrame, Page, etc. These collection properties make it possible to refer to the contained member objects without knowing anything about them, such as their names or the number of them that exist. Using these collections makes it possible to write code that will work for a variety of configurations of the container.
The Columns collection is used in the Init and BeforeRowColChange events to allow the class to function properly regardless of the number, or sequence, of the columns at runtime.
In addition to using the collections that Visual FoxPro provides for us we can also create our own collections. Adding custom array properties to our classes that hold object references to a collection of objects can allow us to handle a variety of situations without „hard coding“ the object references.
Using indirect referencing to objects removes dependency on the names of those objects. Depending on the names of anything in a class definition limits the reusability of that class. Instead of coding the names of things into a class use the THIS, THISFORM, and THISFORMSET keywords. One of the most common questions I have dealt with during a training class is; „How do I get the name of my form so I can refer to it?“. Further questioning usually finds out that the student wants to change a property of the form from one of the controls inside the form. Using THISFORM makes this question moot, the name of the form is not needed. In fact the form can have its named changed and the code still works correctly.
Another problem that can arise is when an object needs to communicate with its container. I have seen folks write code in the textbox of a column in a grid that is like this;
The problem with the code segment above is that if you place the grid on a page in a PageFrame this code will fail. It fails because the address of the grid has changed from THISFORM.Grid1 to THISFORM. PageFrame1.Page1.Grid1. The above code makes reusing the grid impossible unless it is located in exactly the same place. Rewriting the above code using the Parent property of the object makes the code movable.
This code still refers to the grid however it addresses the grid from the textbox instead of from the form thus allowing the grid to be placed anywhere without affecting the operation of the code in the textbox.
Composition is the building of a complex class by combining various smaller, simpler, classes into a container class. Through composition you can create a single class that has many controls in it. Dropping this single class onto a form will give you a relatively complex interface without needing to build each individual piece of that interface for every form that uses it.
The progress bar class is a complex class made up of a number of simpler classes placed into a container class.
NOTE: Composition, while it can enhance reusability in certain situations, can also present problems. Late composition is much better than early composition. The late and early designations for composition refer to where in the class tree the container is populated with its member objects. If this population takes place just before the class is used to instantiate an object it is called late composition, if this takes place prior to subclasses of the container being created it is called early composition. Early composition causes problems with the flexibility of the composite class by making changes to the member objects more difficult to achieve.
Mediation is allowing one object to handle the communications for other objects. It is most often seen in containers that provide outside objects with an interface for accessing the behaviors of the contained objects.
All communications from outside objects is through methods of the cntProgress container. To increase the width of the shape inside the container the container’s methods are called.
By standardizing on a framework for all application development the contracts between the various classes can be documented and complied with universally. All object oriented systems derive their behavior through objects communicating with other objects. Without object communication there is no activity in the system.
As with human communication, objects must agree on certain conventions in their interobject communications. If two people try to communicate and they don’t first agree on the language to be spoken, there is a possibility that no exchange of ideas will occur because the two people cannot understand each other. The same is true with objects. They must have an agreement on the „language“ or syntax that their communications will use. This agreement is often referred to as a contract between the objects.
Establishing the contracts for object communications is one of the many things that an application framework does for us. By standardizing on a framework to be used by all application development work we assure that the interobject communication contracts will be consistent and can be depended upon.
Whenever we start to create a reusable class definition we must decide whether this class will require the contracts of the framework or will function properly regardless of the framework. If we decide the class must function outside of the framework, then we have to take measures to assure that the class has no dependencies on the framework contracts.
The principles of object oriented development, such as Inheritance, Polymorphism, Encapsulation, Mediation, and Composition, foster the creation of reusable classes. By applying these principles effectively the classes we create can be used many times over in a variety of different settings. However, this reusability does not come for free, we must actively create our classes to be reusable by paying close attention to our design and coding.
There are degrees of reusability, such as reusable within the confines of a particular framework versus reusable across multiple frameworks. The higher the degree of reusability, the more we must plan for it when creating the class.