《GOF设计模式》—原型(Prototype)—Delphi源码示例:乐谱编辑器

示例:乐谱编辑器

实现:

你可以通过定制一个通用的图形编辑器框架和增加一些表示音符、休止符和五线谱的新对象来构造一个乐谱编辑器。这个编辑器框架可能有一个工具选择板用于将这些音乐对象加到乐谱中。这个选择板可能还包括选择、移动和其他操纵音乐对象的工具。用户可以点击四分音符工具并使用它将四分音符加到乐谱中。或者他们可以使用移动工具在五线谱上上下移动一个音符,从而改变它的音调。

我们假定该框架为音符和五线谱这样的图形构件提供了一个抽象的Graphics类。此外,为定义选择板中的那些工具,还提供一个抽象类Tool。该框架还为一些创建图形对象实例并将它们加入到文档中的工具预定义了一个GraphicTool子类。

GraphicTool给框架设计者带来一个问题。音符和五线谱的类特定于我们的应用,而GraphicTool类却属于框架。GraphicTool不知道如何创建我们的音乐类的实例,并将它们添加到乐谱中。我们可以为每一种音乐对象创建一个GraphicTool的子类,但这样会产生大量的子类,这些子类仅仅在它们所初始化的音乐对象的类别上有所不同。我们知道对象复合是比创建子类更灵活的一种选择。问题是,该框架怎么样用它来参数化GraphicTool的实例,而这些实例是由Graphic类所支持创建的。

解决办法是让GraphicTool通过拷贝或者“克隆”一个Graphic子类的实例来创建新的Graphic,我们称这个实例为一个原型。GraphicTool将它应该克隆和添加到文档中的原型作为参数。如果所有Graphic子类都支持一个Clone操作,那么GraphicTool可以克隆所有种类的Graphic,如下图所示。

因此在我们的音乐编辑器中,用于创建个音乐对象的每一种工具都是一个用不同原型进行初始化的GraphicTool实例。通过克隆一个音乐对象的原型并将这个克隆添加到乐谱中,每个GraphicTool实例都会产生一个音乐对象。

我们甚至可以进一步使用Prototype模式来减少类的数目。我们使用不同的类来表示全音符和半音符,但可能不需要这么做。它们可以是使用不同位图和时延初始化的相同的类的实例。一个创建全音符的工具就是这样的GraphicTool,它的原型是一个被初始化成全音符的MusicalNote。这可以极大的减少系统中类的数目,同时也更易于在音乐编辑器中增加新的音符。

特点:

1)、改变值以指定新对象

高度动态的系统允许你通过对象组合定义新的行为。例如,你可以通过实例化已有类并且将这些实例注册为客户对象的原型,就可以有效定义新类别的对象。客户可以将职责代理给原型,从而表现出新的行为。这种设计使得用户无需编程即可定义新“类”。Prototype模式可以极大的减少系统所需要的类的数目。

例如,在我们的音乐编辑器中,通过注册不同音乐对象原型,一个GraphicTool类可以创建无数种音乐对象。

代码:

 

unit uMusicEditor;

 

interface

 

uses

    Windows,Classes,Graphics,Types;

 

type

    TGraphic = class

    private

        FCanvas: TCanvas;

        FPosition: TPoint;

    protected

        function GetName: string; virtual; abstract;

        function GetRect: TRect; virtual; abstract;

    public

        constructor Create(ACanvas: TCanvas);

        //---

        function Clone: TGraphic; virtual; abstract;

        procedure Draw(const APosition: TPoint); virtual; abstract;

        function PtInGraphic(const APosition: TPoint): boolean; virtual; abstract;

        //---

        property Canvas: TCanvas read FCanvas;

        property Name: string read GetName;

        property Position: TPoint read FPosition;

        property Rect: TRect read GetRect;

    end;

    {五线谱}

    TStaff = class(TGraphic)

    protected

        function GetName: string; override;

        function GetRect: TRect; override;

    public

        function Clone: TGraphic; override;

        procedure Draw(const APosition: TPoint); override;

        function PtInGraphic(const APosition: TPoint): boolean; override;

    end;

    {音符}

    TMusicalNote = class(TGraphic)

    end;

    {全音符}

    TWholeNote = class(TMusicalNote)

    protected

        function GetName: string; override;

        function GetRect: TRect; override;

    public

        function Clone: TGraphic; override;

        procedure Draw(const APosition: TPoint); override;

        function PtInGraphic(const APosition: TPoint): boolean; override;

    end;

    {半音符}

    THalfNote = class(TMusicalNote)

    protected

        function GetName: string; override;

        function GetRect: TRect; override;

    public

        function Clone: TGraphic; override;

        procedure Draw(const APosition: TPoint); override;

        function PtInGraphic(const APosition: TPoint): boolean; override;

    end;

 

    TDrawing = class

    private

        FGraphics: TList;

        FPosition: TPoint;

        FSelectIndex: Integer;

        procedure Clear;

        function GetCount: Integer;

        function GetItems(Index: Integer): TGraphic;

        function GetSelection: TGraphic;

    public

        constructor Create;

        destructor Destroy; override;

        //---

        procedure Insert(AGraphic: TGraphic);

        property Count: Integer read GetCount;

        //---

        property Items[Index: Integer]: TGraphic read GetItems;

        property SelectIndex: Integer read FSelectIndex write FSelectIndex;

        property Selection: TGraphic read GetSelection;

        property Position: TPoint read FPosition write FPosition;

    end;

 

    {工具}

    TTool = class

    private

        FDrawing: TDrawing;

    protected

        function GetName: string; virtual; abstract;

    public

        constructor Create(ADrawing: TDrawing);

        //---

        procedure Manipulator; virtual; abstract;

        //---

        property Name: string read GetName;

    end;

    {选择工具}

    TSelectTool = class(TTool)

    protected

        function GetName: string; override;

    public

        procedure Manipulator; override;

    end;

    {旋转工具}

    TRotateTool = class(TTool)

    protected

        function GetName: string; override;

    public

        procedure Manipulator; override;

    end;

    {图形工具}

    TGraphicTool = class(TTool)

    private

        FPrototype: TGraphic;

    protected

        function GetName: string; override;

    public

        constructor Create(ADrawing: TDrawing; APrototype: TGraphic);

        destructor Destroy; override;

        //---

        procedure Manipulator; override;

    end;

 

    TTools = class

    private

        FSelectIndex: Integer;

        FTools: TList;

        procedure Clear;

        function GetCount: Integer;

        function GetItems(Index: Integer): TTool;

        function GetSelection: TTool;

    public

        constructor Create;

        destructor Destroy; override;

        //---

        procedure Add(ATool: TTool);

        //---

        property Items[Index: Integer]: TTool read GetItems;

        property Count: Integer read GetCount;

        property SelectIndex: Integer read FSelectIndex write FSelectIndex;

        property Selection: TTool read GetSelection;

    end;

 

procedure ClearBackground(ACanvas: TCanvas);

 

implementation

 

procedure ClearBackground(ACanvas: TCanvas);

begin

    with ACanvas do

    begin

        with Brush do

        begin

            Color := clBlack;

            Style := bsSolid;

        end;

        FillRect(ClipRect);

    end;

end;

 

constructor TGraphic.Create(ACanvas: TCanvas);

begin

    FCanvas := ACanvas;

end;

 

function TStaff.Clone: TGraphic;

begin

    Result := TStaff.Create(FCanvas);

end;

 

procedure TStaff.Draw(const APosition: TPoint);

var

    i,Y,ARowHeight: integer;

    ARect: TRect;

begin

    FPosition := APosition;

    //---

    with FCanvas do

    begin

        with Pen do

        begin

            Color := clYellow;

            Style := psSolid;

            Width := 1;

            Mode := pmXor;

        end;

        //---

        ARect := self.Rect;

        ARowHeight := (ARect.Bottom - ARect.Top) div 4;

        //---

        Y := ARect.Top;

        //---

        MoveTo(ARect.Left,Y);

        LineTo(ARect.Right,Y);

        //---

        for i := 1 to 4 do

        begin

            Y := Y + ARowHeight;

            //---

            MoveTo(ARect.Left,Y);

            LineTo(ARect.Right,Y);

        end;

    end;

end;

 

function TStaff.GetName: string;

begin

    Result := '五线谱';

end;

 

function TStaff.GetRect: TRect;

const

    CNT_Width = 120;

    CNT_Height = 15;

begin

    Result.Left := FPosition.X - CNT_Width;

    Result.Right := FPosition.X + CNT_Width;

    Result.Top := FPosition.Y - CNT_Height * 2;

    Result.Bottom := FPosition.Y + CNT_Height * 2;

end;

 

function TStaff.PtInGraphic(const APosition: TPoint): boolean;

var

    ALineRect: TRect;

begin

    ALineRect := self.Rect;

    ALineRect.Top := FPosition.Y - 5;

    ALineRect.Bottom := FPosition.Y + 5;

    //---

    Result := Windows.PtInRect(ALineRect,APosition);

end;

 

function TWholeNote.Clone: TGraphic;

begin

    Result := TWholeNote.Create(FCanvas);

end;

 

function TWholeNote.GetRect: TRect;

const

    CNT_Radius = 10;

begin

    with FPosition do

        Result := Classes.Rect(X - CNT_Radius,Y - CNT_Radius,X + CNT_Radius,Y + CNT_Radius);

end;

 

procedure TWholeNote.Draw(const APosition: TPoint);

begin

    FPosition := APosition;

    //---

    with FCanvas do

    begin

        with Pen do

        begin

            Color := clRed;

            Style := psSolid;

            Width := 2;

            Mode := pmXor;

        end;

        Brush.Style := bsClear;

        //---

        Ellipse(self.Rect);

    end;

end;

 

function TWholeNote.GetName: string;

begin

    Result := '全音符';

end;

 

function TWholeNote.PtInGraphic(const APosition: TPoint): boolean;

var

    Rgn: HRGN;

begin

    Rgn := CreateEllipticRgnIndirect(self.Rect);

    try

        Result := Windows.PtInRegion(Rgn,APosition.X,APosition.Y);

    finally

        DeleteObject(Rgn);

    end;

end;

 

function THalfNote.Clone: TGraphic;

begin

    Result := THalfNote.Create(FCanvas);

end;

 

function THalfNote.GetRect: TRect;

const

    CNT_Radius = 10;

begin

    with FPosition do

        Result := Classes.Rect(X - CNT_Radius,Y - CNT_Radius,X + CNT_Radius,Y + CNT_Radius);

end;

 

procedure THalfNote.Draw(const APosition: TPoint);

var

    pts: array[0..2] of TPoint;

begin

    FPosition := APosition;

    //---

    with FCanvas do

    begin

        with Pen do

        begin

            Color := clRed;

            Style := psSolid;

            Width := 2;

            Mode := pmXor;

        end;

        Brush.Style := bsClear;

        //---

        with self.Rect do

        begin

            pts[0].X := Left;

            pts[0].Y := Bottom;

            pts[1].X := Right;

            pts[1].Y := Bottom;

            pts[2].X := (Left + Right) div 2;

            pts[2].Y := Top;

        end;

        //---

        Polygon(pts);

    end;

end;

 

function THalfNote.GetName: string;

begin

    Result := '半音符';

end;

 

function THalfNote.PtInGraphic(const APosition: TPoint): boolean;

var

    Rgn: HRGN;

    pts: array[0..2] of TPoint;

begin

    with self.Rect do

    begin

        pts[0].X := Left;

        pts[0].Y := Bottom;

        pts[1].X := Right;

        pts[1].Y := Bottom;

        pts[2].X := (Left + Right) div 2;

        pts[2].Y := Top;

    end;

    //---

    Rgn := CreatePolygonRgn(pts,Length(pts),WINDING);

    try

        Result := Windows.PtInRegion(Rgn,APosition.X,APosition.Y);

    finally

        DeleteObject(Rgn);

    end;

end;

 

constructor TTool.Create(ADrawing: TDrawing);

begin

    FDrawing := ADrawing;

end;

 

constructor TGraphicTool.Create(ADrawing: TDrawing; APrototype: TGraphic);

begin

    inherited Create(ADrawing);

    //---

    FPrototype := APrototype;

end;

 

destructor TGraphicTool.Destroy;

begin

    FPrototype.Free;

    //---

    inherited;

end;

 

function TGraphicTool.GetName: string;

begin

    Result := FPrototype.Name;

end;

 

procedure TGraphicTool.Manipulator;

var

    p: TGraphic;

begin

    p := FPrototype.Clone;

    //---

    p.Draw(FDrawing.Position);

    //---

    FDrawing.Insert(p);

end;

 

procedure TDrawing.Clear;

var

    i: Integer;

begin

    with FGraphics do

    begin

        for i := 0 to Count - 1 do

            TObject(Items[i]).Free;

        //---

        Clear;

    end;

end;

 

constructor TDrawing.Create;

begin

    FGraphics := TList.Create;

    FSelectIndex := -1;

end;

 

destructor TDrawing.Destroy;

begin

    self.Clear;

    FGraphics.Free;

    //---

    inherited;

end;

 

function TDrawing.GetCount: Integer;

begin

    Result := FGraphics.Count;

end;

 

function TDrawing.GetItems(Index: Integer): TGraphic;

begin

    Result := FGraphics[Index];

end;

 

function TDrawing.GetSelection: TGraphic;

begin

    if FSelectIndex >= 0 then

        Result := FGraphics[FSelectIndex]

    else

        Result := nil;

end;

 

procedure TDrawing.Insert(AGraphic: TGraphic);

begin

    FGraphics.Add(AGraphic);

end;

 

constructor TTools.Create;

begin

    FTools := TList.Create;

end;

 

destructor TTools.Destroy;

begin

    self.Clear;

    FTools.Free;

    //---

    inherited;

end;

 

procedure TTools.Clear;

var

    i: Integer;

begin

    with FTools do

    begin

        for i := 0 to Count - 1 do

            TObject(Items[i]).Free;

        //---

        Clear;

    end;

end;

 

function TTools.GetItems(Index: Integer): TTool;

begin

    Result := FTools[Index];

end;

 

procedure TTools.Add(ATool: TTool);

begin

    FTools.Add(ATool);

end;

 

function TTools.GetCount: Integer;

begin

    Result := FTools.Count;

end;

 

function TTools.GetSelection: TTool;

begin

    if FSelectIndex >= 0 then

        Result := FTools[FSelectIndex]

    else

        Result := nil;

end;

 

function TRotateTool.GetName: string;

begin

    Result := '旋转';

end;

 

procedure TRotateTool.Manipulator;

begin

 

end;

 

function TSelectTool.GetName: string;

begin

    Result := '选择';

end;

 

procedure TSelectTool.Manipulator;

    //---

    procedure _SelectGraphic;

    begin

        with FDrawing.Selection do

        begin

            with Canvas do

            begin

                Brush.Color := clWhite;

                DrawFocusRect(Rect);

            end;

        end;

    end;

    //---

    procedure _NoSelectGraphic;

    begin

        _SelectGraphic;

    end;

var

    i: integer;

begin

    with FDrawing do

    begin

        if SelectIndex >= 0 then

        begin

            _NoSelectGraphic;

            SelectIndex := -1;

        end;

        //---

        for i := 0 to Count - 1 do

        begin

            if Items[i].PtInGraphic(Position) then

            begin

                SelectIndex := i;

                _SelectGraphic;

                //---

                break;

            end;

        end;

    end;

end;

 

end.

 

unit Unit1;

 

interface

 

uses

    Windows,Messages,SysUtils,Classes,Graphics,Controls,Forms,Dialogs,

    StdCtrls,ExtCtrls,uMusicEditor, ToolWin, ComCtrls;

 

type

    TForm1 = class(TForm)

        ToolBar1: TToolBar;

        Image1: TImage;

        procedure FormDestroy(Sender: TObject);

        procedure FormCreate(Sender: TObject);

        procedure FormResize(Sender: TObject);

        procedure Image1MouseUp(Sender: TObject; Button: TMouseButton; Shift:

            TShiftState; X, Y: Integer);

        procedure ToolButton1Click(Sender: TObject);

    private

        FTools: TTools;

        FDrawing: TDrawing;

    public

    { Public declarations }

    end;

 

var

    Form1: TForm1;

 

implementation

 

{$R *.DFM}

 

procedure TForm1.FormCreate(Sender: TObject);

    //---

    procedure _InitTools;

    begin

        FTools := TTools.Create;

        with FTools do

        begin

            Add(TSelectTool.Create(FDrawing));

            Add(TRotateTool.Create(FDrawing));

            Add(TGraphicTool.Create(FDrawing,TStaff.Create(Image1.Canvas)));

            Add(TGraphicTool.Create(FDrawing,THalfNote.Create(Image1.Canvas)));

            Add(TGraphicTool.Create(FDrawing,TWholeNote.Create(Image1.Canvas)));

        end;

    end;

    //---

    procedure _ShowTools;

    var

        i: integer;

        AButton: TToolButton;

    begin

        with FTools do

        begin

            for i := 0 to Count - 1 do

            begin

                AButton := TToolButton.Create(ToolBar1);

                with AButton do

                begin

                    Caption := Items[i].Name;

                    Tag := i;

                    Style := tbsCheck;

                    Grouped := true;

                    OnClick := self.ToolButton1Click;

                    Parent := ToolBar1;

                end;

            end;

        end;

    end;

begin

    ToolBar1.Align := alLeft;

    with ToolBar1 do

    begin

        ShowCaptions := true;

        AutoSize := true;

    end;

    //---

    FDrawing := TDrawing.Create;

    //---

    _InitTools;

    _ShowTools;

end;

 

procedure TForm1.FormDestroy(Sender: TObject);

begin

    FTools.Free;

    FDrawing.Free;

end;

 

procedure TForm1.FormResize(Sender: TObject);

begin

    with Image1.Picture.Graphic do

    begin

        Height := Image1.Height;

        Width := Image1.Width;

    end;

    ClearBackground(Image1.Canvas);

end;

 

procedure TForm1.Image1MouseUp(Sender: TObject; Button: TMouseButton; Shift:

    TShiftState; X, Y: Integer);

begin

    FDrawing.Position := Point(X, Y);

    FTools.Selection.Manipulator;

end;

 

procedure TForm1.ToolButton1Click(Sender: TObject);

begin

    FTools.SelectIndex := (Sender as TToolButton).Tag;

end;

 

end.

窗体文件:

object Form1: TForm1

  Left = 294

  Top = 230

  Width = 442

  Height = 296

  Caption = 'Prototype'

  Color = clBtnFace

  Font.Charset = DEFAULT_CHARSET

  Font.Color = clWindowText

  Font.Height = -11

  Font.Name = 'MS Sans Serif'

  Font.Style = []

  OldCreateOrder = False

  OnCreate = FormCreate

  OnDestroy = FormDestroy

  OnResize = FormResize

  PixelsPerInch = 96

  TextHeight = 13

  object Image1: TImage

    Left = 121

    Top = 0

    Width = 313

    Height = 262

    Align = alClient

    AutoSize = True

    OnMouseUp = Image1MouseUp

  end

  object ToolBar1: TToolBar

    Left = 0

    Top = 0

    Width = 121

    Height = 262

    Align = alLeft

    ButtonHeight = 7

    ButtonWidth = 8

    Caption = 'ToolBar1'

    ShowCaptions = True

    TabOrder = 0

  end

end

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值