Composite Design Pattern in Delphi

The Composite Design Pattern lets clients treat individual objects and compositions of objects uniformly

The idea is to define a Composite Class

AComponentClass = class 
   //...
   procedure DoSomething; virtual; 
end;

ACompositeClass = class(aComponentClass) 
    aList: tList;  // List of "aComponentClass" Objects 
    procedure DoSomething; override; 
    //... 
end;

Now you can call a single class method aComponentClass.DoSomething; in any case, whether the class is a composite class or not. The method "DoSomething" of the CompositeClass for example calls "DoSomething" for every item of the list.

This allows you to ignore the difference between compositions of objects and individual objects.

A simple example for a composition is a directory - a directory is a composition of files. A simple implementation of a class that copies a file or a complete directory with all subdirectories looks like this:

uses Classes,SysUtils,..; 

  TFile = class 
  public 
    fName : string; 
  public 
    constructor Create(Name : string); 
    procedure Copy(DstDir : string); virtual; 

    property Name : string read fName; 
  end; 

  TDirectory = class(TFile)   
  private 
    FileList : TList; 
  public 
    constructor Create(Name : string); 
    destructor Destroy; 
    procedure Copy(DstDir : string); override; 

    property Name; 
  end; 

// TFile

constructor TFile.Create(Name: string); 
begin 
  fName:=Name; 
end; 

procedure TFile.Copy(DstDir: string); 
var SrcFilename,DstFilename : string; 
begin 
  SrcFilename:=fName; 
  DstFilename:=IncludeTrailingPathDelimiter(DstDir)+ 
               ExtractFilename(fName); 
  if FileExists(SrcFilename) then 
    Windows.CopyFile(PChar(SrcFilename),PChar(DstFilename),false); 
end; 

// TDirectory

procedure TDirectory.Copy(DstDir: string); 
var i : integer; 
    RelPath, Separator : string; 
begin 
  if not DirectoryExists(DstDir) then 
    ForceDirectories(DstDir); 

  for i:=0 to FileList.Count-1 do 
   if TFile(FileList[i]) is tDirectory then 
   begin 
     RelPath:=ExtractRelativePath(IncludeTrailingPathDelimiter(Name),  TDirectory(FileList[i]).Name);
     Separator := '\'; //It's a filename separator
     TDirectory(FileList[i]).Copy(DstDir+Separator+RelPath)
   end else 
     TFile(FileList[i]).Copy(DstDir);
end; 

constructor TDirectory.Create(Name: string); 
var Root,s : string; 
    sr : tSearchRec; 
begin 
  inherited Create(Name); 

  FileList := TList.Create; 

  Root:=IncludeTrailingPathDelimiter(Name); 
  s:=Root+'*.*'; 
  if FindFirst(s, faAnyFile , sr) = 0 then 
  begin 
    repeat 
      if (sr.Name = '.') or (sr.Name = '..') then continue; 

      if ((sr.Attr and faDirectory) <> 0) then
         FileList.Add(tDirectory.Create(Root+sr.Name)) 
      else
         FileList.Add(tFile.Create(Root+sr.Name)); 
    until FindNext(sr) <> 0; 
    FindClose(sr); 
  end; 
end; 

destructor TDirectory.Destroy; 
var i : integer; 
begin 
  for i:=0 to FileList.Count-1 do 
    tFile(FileList[i]).Destroy; 
  FileList.Free; 
end;

Code examples