Thursday, June 30, 2011

Backward Compatibility using Defalt Parameters

When a function, procedure, or method requires at least one parameter, the final (right-most) parameters in the parameter list can be declared to have a default value. When the parameters declared to have a default value are omitted during the invocation of the subroutine, the default values are assigned to the corresponding formal parameters.

While this feature is interesting by itself, there is another use for this feature that permits you to extend an existing method without breaking older versions.

The best way to explain this trick is with an example. Imaging that you have a method you call to display an error message on your page. This method takes a single string parameter, and assigns that value to one or more Label controls (or some other display element).

Here is an example of what the implementation of that method might look like:

procedure TForm1.SetMessageText(Value: String);
begin
  MessageLabel1.Caption := Value;
  MessageLabel2.Caption := Value;
end;

Let's assume that these Label controls use a red foreground color, so that when a non-empty string is assigned the message stands out, and looks like an error message. Assigning an empty string to the method sets the Labels's caption property to an empty string, effectively turning off the message.

So, whenever you need to display an error message, you simply call this method, using something similar to the following:

SetMessageText('You must enter a starting date before continuing');

Now image that at some later time you realize that you want to be able to display non-error messages using these same Labels. These non-error messages should use a black font for the Labels, and not a red one.

Instead of create a new method, you can simply extend the existing method easily by adding a new parameter. Importantly, in order to avoid having to go back and modify your existing calls to SetMessageText, this second parameter will use a default value to direct the method to use a red font when this parameter is absent.

Here is how the updated method implementation might look:

procedure TForm1.SetMessageText(Value: String; IsError: Boolean = True);
begin
  if IsError then
  begin
    MessageLabel1.Font.Color := clRed;
    MessageLabel2.Font.Color := clRed;
  end
  else
  begin
    MessageLabel1.Font.Color := clBlack; 
    MessageLabel2.Font.Color := clBlack;
  end;
  MessageLabel1.Caption := Value;
  MessageLabel2.Caption := Value;
end;
Now, when you use a call something like the following, your message will be display in a black font, indicating that this is an informational message:

SetMessageText('The time is ' + DateTimeToStr(Now), False);

Any call that omits the second parameter, or calls that include True in the second parameter, will display the message in a red font.

There are several restrictions concerning the use of default parameters. These are:
  • The specified default values must be represented by a constant expression
  • Parameters that are object types, variants, records, or static arrays cannot have default values
  • Parameters that are class, class reference, dynamic arrays, and procedural types can only have nil as their default value