• Aucun résultat trouvé

Type-Safe Containers and Lists

Dans le document The Structure of the Book (Page 166-169)

Containers and lists have a problem: They are not type-safe, as I've shown in both examples by adding a button object to a list of dates. To ensure that the data in a list is homogenous, you can check the type of the data you extract before you insert it, but as an extra safety measure you might also want to check the type of the data while extracting it. However, adding run-time type checking slows a program and is risky a programmer might fail to check the type in some cases.

To solve both problems, you can create specific list classes for given data types and fashion the code from the existing TList or TObjectList class (or another container class). There are two approaches to accomplish this:

Derive a new class from the list class and customize the Add method and the access methods, which relate to the Items property. This is also the approach used by Borland for the container classes, which all derive from TList.

Note

Delphi container classes use static overrides to perform simple type conveniences (parameters and function results of the desired type). Static overrides are not the same as polymorphism; someone using a container class via a TList variable will not be calling the container's specialized functions. Static override is a simple and effective technique, but it has one very important restriction: The methods in the descendent should not do

anything beyond simple typecasting, because you have no guarantee the descendent methods will be called. The list might be accessed and manipulated using the ancestor methods as much as by the descendent methods, so their operations must be identical. The only difference is the type used in the descendent methods, which allows you to avoid extra typecasting.

Create a brand-new class that contains a TList object, and map the methods of the new class to the internal list using proper type checking. This approach defines a wrapper class, a class that "wraps" around an

existing one to provide a different or limited access to its methods (in our case, to perform a type conversion).

I've implemented both solutions in the DateList example, which defines lists of TDate objects. In the code that follows, you'll find the declaration of the two classes, the inheritance-based TDateListI class and the wrapper class TDateListW:

type

// inheritance-based

TDateListI = class (TObjectList) protected

procedure SetObject (Index: Integer; Item: TDate);

function GetObject (Index: Integer): TDate;

public

function Add (Obj: TDate): Integer;

procedure Insert (Index: Integer; Obj: TDate);

property Objects [Index: Integer]: TDate read GetObject write SetObject; default;

end;

function GetObject (Index: Integer): TDate;

procedure SetObject (Index: Integer; Obj: TDate);

function GetCount: Integer;

public

constructor Create;

destructor Destroy; override;

function Add (Obj: TDate): Integer;

function Remove (Obj: TDate): Integer;

function IndexOf (Obj: TDate): Integer;

property Count: Integer read GetCount;

property Objects [Index: Integer]: TDate read GetObject write SetObject; default;

end;

Obviously, the first class is simpler to write it has fewer methods, and they just call the inherited ones. The good thing is that a TDateListI object can be passed to parameters expecting a TList. The problem is that the code that

manipulates an instance of this list via a generic TList variable will not be calling the specialized methods, because they are not virtual and might end up adding to the list objects of other data types.

Instead, if you decide not to use inheritance, you end up writing a lot of code; you need to reproduce every one of the original TList methods, simply calling the methods of the internal FList object. The drawback is that the

TDateListW class is not type compatible with TList, which limits its usefulness. It can't be passed as parameter to methods expecting a TList.

Both of these approaches provide good type checking. After you've created an instance of one of these list classes, you can add only objects of the appropriate type, and the objects you extract will naturally be of the correct type.

This technique is demonstrated by the DateList example. This program has a few buttons, a combo box to let a user choose which of the lists to show, and a list box to show the list values. The program stretches the lists by trying to add a button to the list of TDate objects. To add an object of a different type to the TDateListI list, you can convert the list to its base class, TList. This might happen accidentally if you pass the list as a parameter to a method that expects an ancestor class of the list class. In contrast, for the TDateListW list to fail, you must explicitly cast the object to TDate before inserting it, something a programmer should never do:

procedure TForm1.ButtonAddButtonClick(Sender: TObject);

The UpdateList call triggers an exception, displayed directly in the list box, because I've used an as typecast in the custom list classes. A wise programmer should never write the previous code. To summarize, writing a custom list for a specific type makes a program much more robust. Writing a wrapper list instead of one that's based on inheritance tends to be a little safer, although it requires more coding.

Note

Instead of rewriting wrapper-style list classes for different types, you can use my List Template Wizard. See Appendix A for details.

Streaming

Another core area of the Delphi class library is its support for streaming, which includes file management, memory, sockets, and other sources of information arranged in a sequence. The idea of streaming is that you move through the data while reading it, much like the Read and Write functions traditionally used by the Pascal language (and discussed in Chapter 12 of Essential Pascal (see Appendix C for availability of this e-book).

Dans le document The Structure of the Book (Page 166-169)

Documents relatifs