示例:基于生成器的迷宫
实现:
我们将定义一个CreateMaze成员函数的变体,它以类MazeBuilder的一个生成器对象作为参数。
MazeBuilder类定义的接口可以创建:1)、迷宫。2)、有一个特定房间号的房间。3)、在有号码的房间之间的门。GetMaze操作返回这个迷宫给客户。MazeBuilder的子类将重定义这些操作,返回它们所创建的迷宫。
MazeBuilder的所有建造迷宫的操作缺省时什么也不做,不将它们定义为纯虚函数是为了便于派生类只重定义它们所感兴趣的那些方法。
注意,生成器隐藏了迷宫的内部表示,即定义房间、门和墙壁的那些类,以及这些部件是如何组装成最终的迷宫的。有人可能猜测到有一些类是用来表示房间和门的,但没有迹象显示哪个类是用来表示墙壁的。这就使得改变一个迷宫的表示方式要容易一些。
像其他创建型模式一样,Builder模式封装了对象是如何被创建的,在这个例子中是通过MazeBuilder所定义的接口来封装的。这就意味着我们可以重用MazeBuilder来创建不同种类的迷宫。
注意:MazeBuilder自己并不创建迷宫,它的主要目的仅仅是为创建迷宫定义一个接口。它主要为方便起见定义一些空的实现。MazeBuilder的子类做实际工作。
子类StandardMazeBuilder是一个创建简单迷宫的实现,它将其正在创建的迷宫放在变量F_currentMaze中。CommonWall是一个功能性操作,它决定两个房间之间的公共墙壁的方位。BuildMaze实例化一个Maze,它将被其他操作装配并最终返回给客户(通过GetMaze)。BuildRoom操作创建一个房间并建造它周围的墙壁。为建造一扇两个房间之间的门,StandardMazeBuilder查找迷宫中的这两个房间并找到它们相邻的墙。
我们本可以将所有的StandardMazeBuilder操作放在Maze中并让每一个Maze创建它自身。但将Maze变得小一些使得它能更容易被理解和修改,而且StandardMazeBuilder易于从Maze中分离。更重要的是,将两者分离使得你可以有多种MazeBuilder,每一种使用不同的房间、墙壁和门的类。
一个更特殊的MazeBuilder是CountingMazeBuilder。这个生成器根本不创建迷宫;它仅仅对已被创建的不同种类的构件进行计数。
代码:
unit uBuilderMaze;
interface
uses
Windows,Messages,SysUtils,Variants,Classes,Graphics,Controls;
type
{房间的四个方向}
TDirection = (North = 0,South = 1,East = 2,West = 3);
const
DirectionNames: array[TDirection] of string = ('北', '南', '东', '西');
type
{迷宫构件}
TMapSite = class
private
FStateMsg: string;
public
function Enter: Boolean; virtual; abstract;
//---
property StateMsg: string read FStateMsg write FStateMsg;
end;
{房间}
TRoom = class(TMapSite)
private
FSides: array[TDirection] of TMapSite;
FRoomNumber: Integer;
protected
function GetSides(Direction: TDirection): TMapSite;
procedure SetSides(Direction: TDirection; const Value: TMapSite);
public
constructor Create(ARoomNumber: integer);
destructor Destroy; override;
//---
function Enter: Boolean; override;
//---
property RoomNumber: Integer read FRoomNumber;
property Sides[Direction: TDirection]: TMapSite read GetSides write SetSides;
end;
{墙壁}
TWall = class(TMapSite)
public
function Enter: Boolean; override;
end;
{门}
TDoor = class(TMapSite)
private
FRoom1,FRoom2: TRoom;
//--门是否开启
FIsOpen: Boolean;
procedure Initialize(room1,room2: TRoom);
public
constructor Create(room1,room2: TRoom); virtual;
//---
function Enter: Boolean; override;
{从一个房间(传入参数)进入另一个房间(输出结果)}
function OtherSideFrom(Room: TRoom): TRoom;
end;
TRoomList = class
private
FItemList: TList;
function GetCount: Integer;
function GetItems(Index: integer): TRoom;
protected
procedure Clear;
public
constructor Create;
destructor Destroy; override;
//---
function Add(const Room: TRoom): integer;
//---
property Count: Integer read GetCount;
property Items[Index: integer]: TRoom read GetItems;
end;
{迷宫组件}
TMaze = class
private
FRooms: TRoomList;
public
constructor Create;
destructor Destroy; override;
//---
{在迷宫中加入一个房间}
procedure AddRoom(Room: TRoom);
{根据房间编号取得房间}
function RoomNo(RoomNumber: Integer): TRoom;
end;
{迷宫生成器}
TMazeBuilder = class
public
procedure BuildMaze; virtual;
procedure BuilderRoom(RoomNumber: integer); virtual;
procedure BuilderDoor(roomFrom,roomTo: integer); virtual;
//---
function GetMaze(): TMaze; virtual;
end;
{标准迷宫生成器}
TStandardMazeBuilder = class(TMazeBuilder)
private
F_currentMaze: TMaze;
function CommonWall(r1,r2: TRoom): TDirection;
public
constructor Create;
//---
procedure BuildMaze; override;
procedure BuilderRoom(RoomNumber: integer); override;
procedure BuilderDoor(roomfrom,roomto: integer); override;
//---
function GetMaze(): Tmaze; override;
end;
{统计迷宫生成器}
TCountingMazeBuilder = class(TMazeBuilder)
private
F_Doors: integer;
F_Rooms: integer;
public
constructor Create;
//---
procedure BuildMaze; override;
procedure BuilderRoom(RoomNumber: integer); override;
procedure BuilderDoor(roomfrom,roomto: integer); override;
procedure AddWall(WallNumber: integer; d: TDirection);
//---
procedure GetCounts(var Rooms,Doors: integer);
end;
{迷宫游戏}
TMazeGame = class
public
function CreateMaze(builder: TMazeBuilder): TMaze;
function CreateComplexMaze(builder: TMazeBuilder): TMaze;
end;
implementation
constructor TRoom.Create(ARoomNumber: integer);
//---
procedure _InitSides;
var
Direction: TDirection;
begin
for Direction := Low(FSides) to High(FSides) do
FSides[Direction] := nil;
end;
begin
inherited Create;
//---
FRoomNumber := ARoomNumber;
//---
_InitSides;
end;
destructor TRoom.Destroy;
//---
procedure _ClearSides;
var
Direction: TDirection;
Door: TDoor;
//---
procedure _ClearDoor(Room: TRoom; Door: TDoor);
var
Direction: TDirection;
begin
if Room <> nil then
begin
for Direction := Low(TDirection) to High(TDirection) do
begin
if Room.Sides[Direction] = Door then
begin
Room.Sides[Direction] := nil;
exit;
end;
end;
end;
end;
begin
for Direction := Low(FSides) to High(FSides) do
begin
if FSides[Direction] <> nil then
begin
if FSides[Direction] is TDoor then
begin
Door := TDoor(FSides[Direction]);
_ClearDoor(Door.OtherSideFrom(self),Door);
end
else
FSides[Direction].Free;
end;
end;
end;
begin
_ClearSides;
//---
inherited;
end;
function TRoom.Enter: Boolean;
begin
self.StateMsg := format('进入房间%d', [FRoomNumber]);
Result := true;
end;
function TRoom.GetSides(Direction: TDirection): TMapSite;
begin
Result := FSides[Direction];
end;
procedure TRoom.SetSides(Direction: TDirection; const Value: TMapSite);
begin
if FSides[Direction] <> nil then
FSides[Direction].Free;
FSides[Direction] := Value;
end;
function TWall.Enter: Boolean;
begin
self.StateMsg := '碰到墙';
Result := false;
end;
constructor TDoor.Create;
begin
inherited Create;
//---
Initialize(room1,room2);
end;
function TDoor.Enter: Boolean;
begin
self.StateMsg := '碰到门';
Result := true;
end;
procedure TDoor.Initialize(room1,room2: TRoom);
begin
FRoom1 := room1;
FRoom2 := room2;
FIsOpen := False;
end;
function TDoor.OtherSideFrom(Room: TRoom): Troom;
begin
if Room = FRoom1 then
Result := FRoom2
else
Result := FRoom1;
end;
constructor TMaze.Create;
begin
inherited;
//---
FRooms := TRoomList.Create;
end;
destructor TMaze.Destroy;
begin
FRooms.Free;
//---
inherited;
end;
procedure TMaze.AddRoom(Room: TRoom);
begin
FRooms.Add(Room);
end;
function TMaze.RoomNo(RoomNumber: Integer): TRoom;
var
i: Integer;
begin
Result := nil;
//---
with FRooms do
begin
for i := 0 to Count - 1 do
begin
if Items[i].Roomnumber = RoomNumber then
begin
Result := Items[i];
Exit;
end;
end;
end;
end;
constructor TRoomList.Create;
begin
inherited;
//---
FItemList := TList.Create;
end;
destructor TRoomList.Destroy;
begin
Clear;
FItemList.Free;
//---
inherited;
end;
function TRoomList.Add(const Room: TRoom): integer;
begin
if Assigned(Room) then
Result := FItemList.Add(Room)
else
Result := -1;
end;
procedure TRoomList.Clear;
var
i: Integer;
begin
with FItemList do
begin
for i := 0 to Count - 1 do
TObject(Items[i]).Free;
//---
Clear;
end;
end;
function TRoomList.GetCount: Integer;
begin
Result := FItemList.Count;
end;
function TRoomList.GetItems(Index: integer): TRoom;
begin
Result := FItemList[Index];
end;
procedure TMazeBuilder.BuildMaze;
begin
end;
procedure TMazeBuilder.BuilderRoom(RoomNumber: integer);
begin
end;
procedure TMazeBuilder.BuilderDoor(roomFrom,roomTo: integer);
begin
end;
function TMazeBuilder.GetMaze: TMaze;
begin
result := nil;
end;
constructor TStandardMazeBuilder.Create;
begin
inherited;
//---
F_currentMaze := nil;
end;
procedure TStandardMazeBuilder.BuildMaze;
begin
F_currentMaze := TMaze.Create;
end;
procedure TStandardMazeBuilder.BuilderRoom(RoomNumber: integer);
var
Room: TRoom;
begin
if F_currentMaze.RoomNo(RoomNumber) = nil then
begin
Room := TRoom.Create(RoomNumber);
F_currentMaze.AddRoom(Room);
//---
Room.SetSides(North,TWall.Create);
Room.SetSides(South,TWall.Create);
Room.SetSides(East,TWall.Create);
Room.SetSides(West,TWall.Create);
end;
end;
procedure TStandardMazeBuilder.BuilderDoor(roomfrom,roomto: integer);
var
r1,r2: TRoom;
d: TDoor;
begin
r1 := F_currentMaze.RoomNo(roomfrom);
r2 := F_currentMaze.RoomNo(roomto);
//---
d := TDoor.Create(r1,r2);
//---
r1.SetSides(CommonWall(r1,r2),d);
r2.SetSides(CommonWall(r2,r1),d);
end;
function TStandardMazeBuilder.CommonWall(r1,r2: TRoom): TDirection;
var
Direction: TDirection;
begin
Result := East;
//---
for Direction := Low(TDirection) to High(TDirection) do
begin
if (r2.Sides[Direction] is TDoor) then
begin
case Direction of
North: Result := South;
South: Result := North;
East: Result := West;
West: Result := East;
end;
//---
exit;
end;
end;
end;
function TStandardMazeBuilder.GetMaze: Tmaze;
begin
result := F_currentMaze;
end;
constructor TCountingMazeBuilder.Create;
begin
inherited;
//---
F_Rooms := 0;
F_Doors := 0;
end;
procedure TCountingMazeBuilder.BuildMaze;
begin
F_Rooms := 0;
F_Doors := 0;
end;
procedure TCountingMazeBuilder.BuilderRoom(RoomNumber: integer);
begin
inc(F_Rooms);
end;
procedure TCountingMazeBuilder.BuilderDoor(roomfrom,roomto: integer);
begin
inc(F_Doors);
end;
procedure TCountingMazeBuilder.AddWall(WallNumber: integer; d: TDirection);
begin
end;
procedure TCountingMazeBuilder.GetCounts(var Rooms,Doors: integer);
begin
Rooms := F_Rooms;
Doors := F_Doors;
end;
function TMazeGame.CreateMaze(builder: TMazeBuilder): TMaze;
begin
builder.BuildMaze;
//---
builder.BuilderRoom(1);
builder.BuilderRoom(2);
builder.BuilderDoor(1,2);
//---
result := builder.GetMaze;
end;
function TMazeGame.CreateComplexMaze(builder: TMazeBuilder): TMaze;
begin
builder.BuildMaze;
//---
builder.BuilderRoom(1);
builder.BuilderRoom(2);
builder.BuilderDoor(1,2);
//---
builder.BuilderRoom(3);
builder.BuilderDoor(2,3);
//......
builder.BuilderRoom(1001);
builder.BuilderDoor(3,1001);
//---
result := builder.GetMaze;
end;
end.
unit Unit1;
interface
uses
Windows,Messages,SysUtils,Classes,Graphics,Controls,Forms,Dialogs,
StdCtrls,uBuilderMaze;
type
TForm1 = class(TForm)
Button1: TButton;
Button2: TButton;
ListBox1: TListBox;
procedure FormDestroy(Sender: TObject);
procedure FormCreate(Sender: TObject);
procedure Button1Click(Sender: TObject);
procedure Button2Click(Sender: TObject);
procedure FormKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState);
procedure ListBox1KeyDown(Sender: TObject; var Key: Word; Shift: TShiftState);
private
FMazeGame: TMazeGame;
FMazeBuilder: TMazeBuilder;
FMaze: TMaze;
FCurRoom: TRoom;
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.DFM}
procedure TForm1.FormCreate(Sender: TObject);
begin
self.KeyPreview := true;
//---
FMazeGame := TMazeGame.Create;
//---
FMazeBuilder := TStandardMazeBuilder.Create;
//FMaze := FMazeGame.CreateMaze(FMazeBuilder);
FMaze := FMazeGame.CreateComplexMaze(FMazeBuilder);
//---
FCurRoom := FMaze.RoomNo(1);
with FCurRoom do
begin
Enter;
ListBox1.Items.Add(StateMsg);
end;
end;
procedure TForm1.FormDestroy(Sender: TObject);
begin
FMaze.Free;
FMazeBuilder.Free;
FMazeGame.Free;
end;
procedure TForm1.FormKeyDown(Sender: TObject; var Key: Word; Shift:
TShiftState);
//---
procedure _EnterRoomSide(Direction: TDirection);
var
ARoom: TRoom;
begin
with FCurRoom do
begin
if Sides[Direction] <> nil then
begin
with Sides[Direction] do
begin
if Enter then
begin
ListBox1.Items.Add(DirectionNames[Direction] + ':' + StateMsg);
//---
if Sides[Direction] is TDoor then
begin
ARoom := TDoor(Sides[Direction]).OtherSideFrom(FCurRoom);
if ARoom <> nil then
begin
if ARoom.Enter then
FCurRoom := ARoom;
ListBox1.Items.Add(ARoom.StateMsg);
end;
end;
end
else
ListBox1.Items.Add(DirectionNames[Direction] + ':' + StateMsg);
end;
end;
end;
end;
begin
case Ord(Key) of
VK_LEFT: _EnterRoomSide(East);
VK_RIGHT: _EnterRoomSide(West);
VK_UP: _EnterRoomSide(South);
VK_DOWN: _EnterRoomSide(North);
end;
end;
procedure TForm1.ListBox1KeyDown(Sender: TObject; var Key: Word; Shift:
TShiftState);
begin
Key := 0;
end;
procedure TForm1.Button1Click(Sender: TObject);
var
Maze: TMaze;
Game: TMazeGame;
builder: TStandardMazeBuilder;
begin
Game := TMazeGame.Create;
builder := TStandardMazeBuilder.Create;
//---
Game.CreateMaze(builder);
Maze := builder.GetMaze;
//---
Maze.free;
builder.free;
Game.free;
end;
procedure TForm1.Button2Click(Sender: TObject);
var
Rooms,Doors: integer;
game: TMazeGame;
builder: TCountingMazeBuilder;
begin
game := TMazeGame.Create;
builder := TCountingMazeBuilder.Create;
//---
game.CreateMaze(builder);
builder.GetCounts(Rooms,Doors);
showmessage(format('The maze has %d rooms and %d doors', [Rooms,Doors]));
//---
Maze.free;
builder.free;
Game.free;
end;
end.