🤖📘🍻 Hooray! After 3 years of work, we've finally released a new ebook on design patterns! Check it out »

State Design Pattern in Delphi

Most programs provide toolbars with different tools. For example Zoom-Buttons, Paint-Buttons (drawing of circles, rectangles,.. ). When the Zoom-Button is pressed, the user can zoom in or out, when the Paint-Button is pressed, the user can paint s.th : each tool changes the behavior of the program. You can realize this through different flags:

procedure TForm.FormMouseDown/FormMouseMove/FormMouseUp 
begin 
  //.. 
  if fZoomFlag then 
  begin 
    //... 
  end else 
  if fPaintFlag then 
  begin 
    //... 
  end; 
  //.. 
end; 

Even if we want to create a simple program like MSPaint, we will need a lot of flags: fPenFlag, fDrawRectFlag, fDrawCircleFlag, fEraserFlag,... This results in 'elephant'-procedures and a general view becomes difficult.

You can avoid such 'elephant'-procedures through the state design pattern. GoF define the State design pattern in this way: it allows an object to alter its behavior when its internal state changes. The object will appear to change its class.

In our example, the behavior for each tool is encapsulated in a different class:

tTool = class 
private 
  fForm : tForm; 
public 
  constructor Create(Form : TForm); 
  destructor Destroy; override; 
  procedure SetCursor; virtual; 
  procedure HandleMouseDown(x,y : integer); virtual; 
  procedure HandleMouseMove(x,y : integer); virtual; 
  procedure HandleMouseUp(x,y : integer); virtual; 
  procedure StopAction; virtual; 
end; 

tZoomTool = class(tTool) 
public 
  procedure SetCursor; override; 
  procedure HandleMouseDown(x,y : integer); override; 
  procedure HandleMouseMove(x,y : integer); override; 
  procedure HandleMouseUp(x,y : integer); override; 
end; 

tPaintTool = class(tTool) 
public 
  procedure SetCursor; override; 
  procedure HandleMouseDown(x,y : integer); override; 
  procedure HandleMouseMove(x,y : integer); override; 
  procedure HandleMouseUp(x,y : integer); override; 
end;

Now we can replace all the flags by a simple parameter fTool of class tTool. Instead of setting different flags, an object of a derived class is created:

procedure TForm.SetZoomModus; 
begin 
  fTool.Destroy; 
  fTool := tZoomTool.Create(Self); 
end; 

procedure TForm.SetPaintModus; 
begin 
  fTool.Destroy; 
  fTool := tPaintTool.Create(Self); 
end; 

So that in the MouseDown/MouseMove/MouseUp events the appropriate method is called:

procedure TForm.FormMouseDown({...} X, Y: Integer); 
begin 
  fTool.HandleMouseDown(x,y); 
end; 

procedure TForm.FormMouseMove({...} X, Y: Integer); 
begin 
  fTool.HandleMouseMove(x,y); 
end; 

procedure TForm.FormMouseUp({...} X, Y: Integer); 
begin 
  fTool.HandleMouseUp(x,y); 
end;

Code examples

More info, diagrams and examples of the State design pattern you can find on our new resource Refactoring.Guru.