When RAD is bad
Choosing object-oriented programming means leaving some
visual programming practices behind you.
One of the unique features of Delphi is that it is at the
same time a visual and an object-oriented development
environment. In fact, every type of component (visual or not)
is implemented as a class. Every time you define a new form,
you are creating a new class. Every component on the screen
and every form is an instance, an object of the component or
form class. Delphi's component model was the first to bridge
object-oriented programming and visual programming. JavaBeans
borrowed a lot from this model. And as for the Microsoft
visual tools, not one comes close to this integration.
In Delphi there isn't even a clear-cut distinction between
classes and components. Technically, components are objects of
classes inheriting from the TComponent class, one of the
topmost classes of the VCL hierarchy. As you probably know,
inheriting from TComponent is required to install your class
or component on the Delphi palette and manage it at design
time. At runtime, all objects (components or not) are treated
equally.
Affects and effects
What are the practical implications of the fact that a
component is an object? It means that you can do in code
whatever you do in the development environment. At run time,
using Object Pascal code, you can dynamically create new
components, place them on forms, and hook into event handlers
you've written. In other words, you can use two totally
different approaches to place a button on a form. You can drag
the button icon over the form at design-time in the Delphi
IDE, or you can write a couple of lines of code to do the same
thing. The simple CreateC
example in my Mastering Delphi 5 book illustrates the
idea.
The visual approach is certainly easier and faster, but
writing code is more flexible and powerful. There is no way to
create programs in Delphi without using objects. It's
impossible. But it's also easier than you might think to screw
it up. The problem is that to simplify coding, Delphi's visual
programming model favors some bad practices. In fact, Delphi
has many default behaviors, which are good for beginners and
produce applications rapidly, but that can be harmful in
complex examples because they violate a key tenet of
object-oriented programming: encapsulation.
The most obvious default to avoid is the automatic creation
of all the forms when the program starts. I've seem programs
that were having resource problems because they created as
many as 300 forms at startup! In Delphi 5 there is now an
environment setting you can use to turn off this default. Use
it. But there is a related problem that has to do more with
object-orientation: the definition of a global variable for
forms.
Handy but harmful
This default is OK for simple programs but a drawback in
larger applications. The fact that the system automatically
declares a global variable for each class is handy but
confusing. Often Delphi newcomers consider Form1 and TForm1 to
be the same thing. They'll refer to the specific object
(Form1) inside the code of the class methods, and that
prevents them from creating multiple copies of the form. Even
worse, they refer to specific global objects of other form
classes. I know this often saves coding time, but it's a poor
practice and increases coupling.
Publishing a component's fields inside a form can also
cause problems because, again, you are increasing coupling.
Although the fields associated with a form and the fields
associated with a component are related, they are two separate
things. Mixing them up, a common error, produces nasty
effects. My rule is never to refer to components of one form
class from another one. How do you accomplish this? You write
properties or methods for the form that then communicate with
the components the form hosts. Components should be
encapsulated, or every change in your user interface will
result in a nightmare.
Here's an example. Suppose you have a main form with a
status bar hosting a status message. From other forms you can
display messages by
writing:
Form1.StatusBar1.Panels[2].Text := 'my
message';
What if you change the user interface,
moving the message to a different panel of the status bar?
What if you replace the status bar with another component, or
you want to log messages, or make any other change in this
structure? You'll have to change this code, which probably
appears in 378 different places throughout all the units of
the program. My suggestion? Add a Message property to the form
and display the message to the status bar when the property
value changes. Take a look at HideComp
which totally removes published fields.
The examples I've mentioned so far are only the tip of the
iceberg; there are far more techniques for producing programs
that conform to the principles of object-oriented programming
using Delphi. I've provided a more complete and detailed list
of good object-oriented programming rules in an article
published in the July 1999 issue of The Delphi Magazine, if you
can find a copy (unfortunately the article isn't available
on-line). Besides the examples mentioned above, I suggested
two other techniques in the article: leveraging visual form
inheritance and writing custom components to share code.
The big picture
Besides using components and objects according to
object-oriented principles , the application's overall
structure should also be object-oriented. This means giving
careful consideration to managing and operating on data in an
object-oriented manner and thinking about where business logic
is implemented. I'm getting more and more involved in these
architectural questions, and I'll let you know my findings in
a future column. If you are interested now, a good starting
point for your research is Scott Ambler's web site.
In my writings and public presentations, I devote a lot of
time to addressing effective object-oriented practices in
Delphi and invariably find out that for a lot of Delphi
programmers this is a totally new concept. The fact that the
IDE favors a more naïve programming approach doesn't help.
Visual programming in Delphi is good, but you should move to
real OOP if you want to unleash Delphi's power.
Originally written for InPublishing LLC for publication by Inprise Corp. Copyright 1999 Inprise Corp.
|