Monday, December 17, 2007

Programming in Delphi sins #3

In this serie I will tell you, what we find a sin, in Delphi programming.

Using published properties of components from another class
When you place components on forms and datamodules in Delphi they are published by default. In other words you can use components in your form that belong to other forms/datamodules. If you do this, those forms will be tightly coupled and therefor this is not quite a good OOP practice.

type
TdmMain
= class(TDataModule)
//These components are published and direct
// accesable by other forms/datamodules
SomeDataSet: TADOTable;
adsAnotherDataSet: TADODataSet;
private
{ Private declarations }
public
{ Public declarations }
end;

Suppose you have a form that uses a datamodule with some dataset on it and you want to show some field value in a label on your form. You could do it like this:


procedure TForm3.SetCaption;
begin
SomeLabel.Caption :
=
dmMain.SomeDataSet.FieldByName(
'SomeField').AsString;
end;


This is bad practice. Suppose that, something simple as the name of the field changes, you would have change both your datamodule, and your form.

Solution:

Use properties and functions (getters), not properties from published components.

In the datamodule:
property SomField : string read GetSomeField;
procedure TdmMain.GetSomeField : String;
begin
Result :
=
SomeDataSet.FieldByName(
'SomeField').AsString;
end;

In your form:
procedure TForm3.SetCaption;
begin
SomeLabel.Caption :
= dmMain.SomeField;
end;

A more dramatic way to avoid use of published properties is to move all published properties into the private section of the class.

This gives you best practice, however if you use dataware controls, you then should couple them manually.

So for the sake of dataware controls we keep them published, but always write properties or getter functions to get values in code.

5 comments:

Andreas Hausladen said...

> A more dramatic way...

Have you actually tested this? First you get the "Class not found" exception because there is no RTTI generated for the form. This can be solved by manually calling RegisterClass.
Then all the now private/protected component fields are NIL and you will jump from one access violation to the next.

Roland said...

--Andreas.
I never used it for real, but I first learned about a way of doing it by a book of Marco Cantu, using the TComponent FindComponent method to set a reference to a component in the private section.

procedure TForm3.FormCreate(Sender: TObject);
begin
Label1 := FindComponent ('Label1') as TLabel;
Label1.Caption := 'Hello World';
end;

initialization
RegisterClasses ([TLabel]);
end.

(http://www.marcocantu.com/code/md5/HIDECOMP.htm)

Fabricio said...

I normally use DataModules as repository of Datasets which I don't want to be cluttering my form. So I have almost 1-by-1 relation between data modules/forms. But I have data modules which have properties very importants to the application (the security datamodule, as a example).

So, I don't see it much as a sin.

Anonymous said...

Properties in Delphi require a lot of typing and scrolling up-and-down, and their maintenance consumes ever-increasing time over the life of a project. Using them needs a good reason. This isn't one of them.

OK, you change the field name. But the property name will now still be wrong. So what have you gained?

The example cited here can be found in most OOP literature over the past decade or so. But technology has moved on. With modern refactoring tools, it's quick and easy to rename a variable that has changed its meaning.

Unless you are building commercial-grade libraries, in most cases its better to make everything public (or published) and just use common sense before changing names willy-nilly. Take advantage of tools available now. You will save yourself so much time in the long run.

Fabricio said...

@Anonymous:
Many times you have to change the name of underlying field, because of a change db design and the meaning of the property remains the same. I have a property named "IdUsuarioConectado" (in English, 'ConnectedUserId'). The underlying dataset changed a few times, but the property remained. I just changed the read function. Of course, I think the approach of Roland a little extreme - but some properties save me a lot of typing in the long run.