PROMOTIONAL ARTICLE |
This article is taken from "Delphi COM Programming" - very successful
book written by Eric Harmon, that was released in January, 2000 (Macmillian).
By the end of May 2000, the polish translation by Paweł Głowacki
(“Programowanie COM w Delphi”) will be available. The material covered in paper deals
with interfaces as a pure Object Pascal language feature. |
Programming with Interfaces
So far in this chapter, we really haven’t looked at any real-life examples of using
interfaces in your Delphi code. Let’s stop now and look at an example that puts interfaces
to good use in a familiar programming concept: sorting.
SortDemo Example: Algorithms with Interfaces
Many programs make use of sorting to some extent. Simpler programs might use a well-placed bubble sort algorithm here or there, whereas more complex programs may require a workhorse quicksort or mergesort algorithm. Usually when you implement a sort routine in your application, it is tightly bound to the data you want to sort. How many times have you written code such as the following?
procedure SortEmployees; // - sort an array of employees by name var I, J: Integer; begin for I := 1 to MAX_EMPLOYEES - 1 do for J := I + 1 to MAX_EMPLOYEES do if EmployeeArray[J].Name < EmployeeArray[I].Name then begin Temp := EmployeeArray[I]; EmployeeArray[I] := EmployeeArray[J]; EmployeeArray[J] := Temp; end; end; |
The only thing this procedure is ever going to sort is an array of employee records. You probably justify implementing the procedure this way because it’s just a quick bubble-sort procedure, and you can bang out the code so fast that you don’t even have to think about it.
Wouldn’t it be nicer to have a stock routine in your bag of tricks that could be used not only for sorting, but also for ordering elements in a sorted list, and anywhere else a comparison function is needed? Using interfaces, you can accomplish this easily.
Let’s define an interface called ICompare. ICompare’s job will be to compare an object (ObjectA) to another similar object (ObjectB), and return -1 if ObjectA comes before ObjectB, 0 if the two objects are equal, or 1 if ObjectA comes after ObjectB. To make this example more useful, I’m going to add to this interface the capability to support multiple ordering methods for an object. Here is the interface declaration:
type ICompare = interface [‘{DDFE0840-E8FB-11D2-9085-0040F6741DE2}’] function CompareWith(ACompareTo: ICompare; AOrderBy: Integer): Integer; end; |
This interface’s sole purpose in life is to provide an object with the means of ordering itself in some way. This illustrates another important point about interfaces: When creating an interface, make it as specific as possible. You don’t want to create the Swiss army knife of interfaces. An interface that is multipurpose will not be useful in very many situations. More than likely, it will be specific only to a single application you’re writing. Instead of one general-purpose interface, create several smaller, more focused interfaces that you can reuse in many different situations.
The usage for this interface will be like this:
if MyObject1.CompareWith(MyObject2, SortIndex) < 0 then // MyObject1 comes before MyObject2 |
The parameter AOrderBy is intended to let this interface’s implementor define many ordering methods. For example, a TStudent class in a college enrollment program might need to order students by name or social security number. The TStudent class can define the number 1 to mean order by name, and the number 2 to mean order by social security number.
Listing 1.7 shows the source code for unit IntfUnit, which declares the ICompare interface and implements a simple, yet reusable, bubble sort.
Listing 1.7 SortDemo Source: intfunit.pas
unit IntfUnit; interface type ICompare = interface [‘{DDFE0840-E8FB-11D2-9085-0040F6741DE2}’] function CompareWith(ACompare: ICompare; ASortBy: Integer): Integer; end; procedure SortArray(var A: Array of IUnknown; ASortBy: Integer);
implementation |
Note that SortArray takes an array of IUnknown. If I passed in an array of TInterfacedObjects, for example, then the following line of code would cause the objects to be destroyed prematurely:
if (A[J] as ICompare).CompareWith(A[I], AOrderBy) < 0 then begin |
The as operator would cause Delphi to call _AddRef and _Release on the ICompare interface, which would destroy all the objects I’m sorting right in the middle of the sort. This is a classic example of when mixing reference models can get you into trouble.
The SortDemo example defines two different classes that implement the ICompare interface: TEmployee and TInventoryItem. These classes are defined in the units EmpUnit and InvUnit, respectively. Listing 1.8 shows the source code for the EmpUnit unit.
Listing 1.8 SortDemo Source: empunit.pas
unit EmpUnit; interface uses SysUtils, IntfUnit; type TEmployeeOrder = (eoName, eoSalary); IEmployee = interface [‘{FFCD24F0-4FE8-11D3-B84D-0040F67455FE}’] function GetName: string; function GetSalary: Double; end;
TEmployee = class(TInterfacedObject, IEmployee, ICompare)
implementation function TEmployee.CompareWith(ACompare: ICompare; ASortBy: Integer): Integer;
constructor TEmployee.Create(AName: string; ASalary: Double);
function TEmployee.GetName: string;
function TEmployee.GetSalary: Double;
end. |
TEmployee is a simple class used to track employee names and salaries. Employees can be sorted by either name or salary. I have implemented the enumerated type TEmployeeOrder to specify valid ordering methods for the TEmployee class.
Listing 1.9 shows the source code for the InvUnit unit.
Listing 1.9 SortDemo Source: invunit.pas
unit InvUnit; interface uses SysUtils, IntfUnit; type TInventoryItemOrder = (iioPartNumber, iioDescription, iioUnitPrice, iioInStock); IInventoryItem = interface [‘{FFCD24F1-4FE8-11D3-B84D-0040F67455FE}’] function GetPartNumber: string; function GetDescription: string; function GetInStock: Integer; function GetUnitPrice: Double; end; TInventoryItem = class(TInterfacedObject, IInventoryItem, ICompare) private FPartNumber: string; FDescription: string; FInStock: Integer; FUnitPrice: Double; public constructor Create(APartNumber: string; ADescription: string; AInStock: Integer; AUnitPrice: Double); function CompareWith(ACompare: ICompare; ASortBy: Integer): Integer; function GetPartNumber: string; function GetDescription: string; function GetInStock: Integer; function GetUnitPrice: Double; end;
implementation
constructor TInventoryItem.Create(APartNumber, ADescription: string; AInStock: Integer; AUnitPrice: Double);
function TInventoryItem.GetDescription: string;
function TInventoryItem.GetInStock: Integer;
function TInventoryItem.GetPartNumber: string;
function TInventoryItem.GetUnitPrice: Double;
end. |
TInventoryItem is similar in nature to TEmployee, but it is used to track inventory items, and can be sorted by part number, description, unit price, or quantity in stock (iioPartNumber, iioDescription, iioUnitPrice, or iioInStock).
The reason for creating these two disparate classes is to show you that you can easily use the same ICompare interface to sort completely independent sets of data.
Listing 1.10 shows the code for SortDemo’s main form.
Listing 1.10 SortDemo Source: mainform.pas
unit MainForm;
const
type
var
implementation
procedure TfrmMain.CreateInventoryItem(AIndex: Integer; APartNo: string; ADescription: string; AInStock: Integer; AUnitPrice: Double);
procedure TfrmMain.FormCreate(Sender: TObject);
// Load employees into the list box end;
procedure TfrmMain.LoadListBox;
procedure TfrmMain.btnByNameClick(Sender: TObject);
procedure TfrmMain.btnBySalaryClick(Sender: TObject);
procedure TfrmMain.lvInventoryColumnClick(Sender: TObject; Column: TListColumn);
procedure TfrmMain.lvInventoryCompare(Sender: TObject; Item1, Item2: TListItem; Data: Integer; var Compare: Integer); end.
|
TfrmMain’s FormCreate method creates an array of employees and an array of inventory items. Obviously, a real application would get its data from a database or data file of some type, but these hardcoded arrays will suffice for demonstration purposes.
When you run this program, you’ll see a TPageControl with two pages on it. The first page contains a TListBox that is used to display the list of employees.
Figure 1.3 shows the first page of the SortDemo application.
Figure 1.3 SortDemo application: Employee list.
By default, the employee names are displayed in the order that we created them. Clicking on the Sort by Name or Sort by Salary buttons sorts the employee array by name or salary, respectively. The sort routine used is a simple bubble sort that orders the objects based on the ICompare interface’s CompareWith method. Click the List View tab to display the list of inventory items, again, in the order that we created them. Click any of the four headers: Part #, Description, Unit Price, or In Stock to sort the list by that column. This code uses the ICompare interface in a completely different manner.
Figure 1.4 shows the second page of the SortDemo application.
Figure 1.4 SortDemo application: Inventory item list.
If you’re not familiar with the TListView’s sorting capabilities, take a look at the following code snippet from Listing 1.10:
procedure TfrmMain.lvInventoryColumnClick(Sender: TObject; Column: TListColumn); begin lvInventory.CustomSort(nil, Column.Index) end;
procedure TfrmMain.lvInventoryCompare(Sender: TObject; Item1, Item2: TListItem; Data: Integer; var Compare: Integer); |
TListView’s CustomSort method uses the method assigned to its OnCompare event to compare two items in the list view. Because I used the Data property of the list view items to hold a pointer to the TInventoryItem’s IUnknown interface, I can simply typecast the Data property back to an IUnknown and call the TInventoryItem.CompareWith method to compare the items.
Obviously, TListView has its own internal sort engine that calls TListView’s OnCompare method at the appropriate times. What’s important to understand here is that by declaring the ICompare interface generically, it can be used in numerous instances. If instead of declaring an ICompare interface, we had declared an ISort interface, then that code would not have been usable in this particular situation.
At this point, we’ve pretty well covered the basics of interfaces. The next section discusses some additional features of interfaces that are slightly more advanced.
END OF PROMOTIONAL ARTICLE |
This article was taken from Eric Harmon’s “Delphi COM Programming” (Macmillian Publishing). Polish translation – “Programowanie COM w Delphi”(TRANSLATOR S.C.) - by Paweł Głowacki will be available by the end of May, 2000. |