This tutorial is about to design and implement a collection class that automatically limit/trim its members.
For simplicity and easy of use, in this tutorial we will create a custom class that wraps an instance of TList. New custom class making sure our class is simple, without many properties and methods that indirectly related. This making sure that we could take advantage of Delphi's IDE autocompletion feature to the fullest. See this post to get a sample of how to take advantage of autocompletion feature. The TList is the one who does the actual job of collecting items internally.
Let's name this class TLimitList. And here the skeleton interface of our class. I believe most of the properties and methods functionality could be easily guessed from their names. But we will explain some important ones.
TLimitList=class public procedure Add(AItem: TObject); procedure Trim; function Delete(AItem: TObject): Boolean; overload; function Delete(AIndex: Integer): Boolean; overload; procedure Clear; property Count: Integer read GetCount; property Capacity: Integer read FCapacity write SetCapacity; property OwnItems: Boolean read FOwnItems write FOwnItems; end;
Basic scenario
Property Capacity defines the maximum number of items that should be hold in the collection. Whenever we add new item or change the capacity value we should inspect the current number of collected items. If we detect the items are more than allowed, we should remove the older ones. The inspection and removals is done by the Trim method.
So the implementation of Add method is:
procedure TLimitList.Add(AItem: TObject); begin FList.Add(AItem); Trim; end;
See that we run Trim immediately after we add new item to our internal list.
And when we change the capacity value we will execute SetCapacity. And here is the implementation of it.
procedure TLimitList.SetCapacity(const Value: Integer); begin if FCapacity <> Value then begin FCapacity := Value; Trim; end; end;
Like what we are doing in Add method, here we also immediately execute Trim when we changing the capacity value to make sure the number of collected items does not exceed the capacity.
And here is the implementation of Trim method.
procedure TLimitList.Trim; begin while FList.Count > FCapacity do Delete(0); end;
Simple, right? It just delete the first item (the oldest item) while the number is exceeding the current capacity.
For full source code of the TLimitList class please check LimitList unit in the attached source code. Feel free to post queries or critics here.
DUnit Testframework
For this tutorial I did not write a customized demo project. This time I use DUnit test framework for testing our TLimitList library. I found using DUnit gave you at least these advantages.
- You make sure your code run as expected, since it is for testing your code.
- You don't have to write special demo project to illustrate your codes.
- You already provided code snippets on using your library/code in your DUnit tests units.
- When you do refactoring your code, the DUnit tests tell us ummediately whether the new changes break existing behavior.
DUnit library itself is a free and open source project. Currently hosted in SourceForge.Net. If you don't have it, go download it here and read the wiki here.
Using DUnit test framework, we create a test class containing methods to test our TLimitList class. In this tutorial we want to test if the add, trim, and clear methods work as expected, and also the count property should return correct amount. So the skeleton of our test class:
TTestLimitList=class(TTestCase) published procedure Test_Add_And_Count; procedure Test_Clear; procedure Test_AutoTrimming; end;
For the automatic recognition of the tests methods, you must declare them parameterless in published section as shown above.
So for the implementations:
procedure TTestLimitList.Test_Add_And_Count; var vItems: array[1..3] of TObject; begin vItems[1] := TObject.Create; vItems[2] := TObject.Create; vItems[3] := TObject.Create; Check(List.Count=0); List.Add(vItems[1]); Check(List.Count=1); List.Add(vItems[2]); Check(List.Count=2); List.Add(vItems[3]); Check(List.Count=3); end;
procedure TTestLimitList.Test_AutoTrimming; const cTestCapacity = 10; var i: Integer; vItems: array[1..100] of TObject; begin // explicitly set the capacity of the collection class List.Capacity := cTestCapacity; // init the test objects for i := 1 to 100 do begin vItems[i] := TObject.Create; List.Add(vItems[i]); Check(List.Count <= cTestCapacity); end; end;
procedure TTestLimitList.Test_Clear; var vItems: array[1..3] of TObject; begin vItems[1] := TObject.Create; vItems[2] := TObject.Create; vItems[3] := TObject.Create; List.Add(vItems[1]); List.Add(vItems[2]); List.Add(vItems[3]); Check(List.Count=3); List.Clear; Check(List.Count=0); end;
Here is our test program in action, before we execute the tests.
[ATTACH]3654[/ATTACH]
And here it is after we execute the test. Note that this prove that the tested methods and property of TLimitList work as expected.
[ATTACH]3653[/ATTACH]
Full source and executable are attached. Fell free to query anything.
[ATTACH]3652[/ATTACH]