Delphi Programming Forum
C++Builder  |  Delphi  |  FireMonkey  |  C/C++  |  Free Pascal  |  Firebird
볼랜드포럼 BorlandForum
 경고! 게시물 작성자의 사전 허락없는 메일주소 추출행위 절대 금지
델파이 포럼
Q & A
FAQ
팁&트릭
강좌/문서
자료실
컴포넌트/라이브러리
FreePascal/Lazarus
볼랜드포럼 홈
헤드라인 뉴스
IT 뉴스
공지사항
자유게시판
해피 브레이크
공동 프로젝트
구인/구직
회원 장터
건의사항
운영진 게시판
회원 메뉴
북마크
델마당
볼랜드포럼 광고 모집

델파이 강좌/문서
Delphi Programming Tutorial&Documents
[97] 실행 코드를 배열에 저장하고 나중에 한꺼번에 실행하기의 문제풀이 1탄
주정섭 [jjsverylong] 5134 읽음    2006-05-02 10:39
DrawJob.zip 12.5KB 예제 프라젝트
[알림]
원래 의도는 문제 출제 글에 답글 형식으로 1탄, 2탄, 3탄을 연재할 예정이었으나 이 게시판이 답변형식을 지원하지 않으므로 새로운 게시물로 이 글을 작성하게 되었슴을 유감스럽게 생각합니다.
----------------------------------------------------------------------------------------------

지난번 문제의 기능 요구 사항을 정리해 보자.

1. 사용자가 임의로 그리기 작업을 요구한다.
2. 이 요구된 그리기 작업들은 배열같은 형태로 보관되어야 한다.
3. 나중에 이를 한꺼번에 사용자가 요구한 순서대로 실행할 수 있어야 한다.

여기서 가장 중요한 것은 2항과 3항의 요구사항 만족이다. 객체지향 패턴 중에서 이 기능을 구현하는 것이 하나 있다. 바로 커맨드 패턴이다. 커맨드 패턴이란 쉽게 이야기해서 어떤 실행 동작을 보관하는 클래스를 말한다. 델파이에는 이미 대표적인 커맨트 패턴의 예가 있다. 바로 TAction 콤퍼넌트다. 아직도 많은 델파이 개발자들이 이 기막힌 Action 콤퍼넌트를 잘 사용하지 않는 것은 매우 유감스럽다.

함수지향언어에서는 실행 코드를 보관하는 방법으로는 함수 포인터가 거의 유일한 방법이지만 객체지향에서는 커맨드 패턴으로 실행 코드를 데이타화 즉 객체화할 수 있다. 이 방법은 함수 포인터보다 월등히 기능확장성이 뛰어나다. 첨부한 예제는 이 커맨드 패턴으로 위 기능들을 구현해 본 것이다.

[예제의 메인 소스 내용]

unit fmMain;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, ExtCtrls, StdCtrls, Buttons;

type
  TForm1 = class(TForm)
    Panel1: TPanel;
    Image1: TImage;
    memo1: TMemo;
    Splitter1: TSplitter;
    BitBtn1: TBitBtn;
    BitBtn2: TBitBtn;
    BitBtn3: TBitBtn;
    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
    procedure BitBtn1Click(Sender: TObject);
    procedure BitBtn2Click(Sender: TObject);
    procedure BitBtn3Click(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

  TEJobKind = (ejkLine, ejkRectangle, ejkEllipse);

  TDrawJob = class
  private
    FCanvas: TCanvas;
    Fx1: Integer;
    Fx2: Integer;
    Fy1: Integer;
    Fy2: Integer;
    Kind: TEJobKind;
  public
    constructor CreateEx(ACanvas: TCanvas; AKind: TEJobKind; x1, y1, x2, y2:
        integer);
    function asString : string;
    procedure Execute;
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

uses TypInfo, Contnrs;

var
  jobList : TObjectList;

function TDrawJob.asString: string;
begin
  Result := Copy(GetEnumName(TypeInfo(TEJobKind), ord(Kind)), 4, 20) +
        Format('(%d, %d, %d, %d)', [Fx1, Fy1, Fx2, Fy2]);
end;

constructor TDrawJob.CreateEx(ACanvas: TCanvas; AKind: TEJobKind; x1, y1, x2,
    y2: integer);
begin
  inherited;
  FCanvas := ACanvas;
  Kind := AKind;
  Fx1 := x1;
  Fx2 := x2;
  Fy1 := y1;
  Fy2 := y2;
end;

procedure TDrawJob.Execute;
begin
  case Kind of
    ejkLine :
    begin
      FCanvas.MoveTo(fx1, fy1);
      FCanvas.LineTo(fx2, fy2);
    end;
    ejkRectangle :
    begin
      FCanvas.Rectangle(fx1, fy1, fx2, fy2);
    end;
    ejkEllipse :
    begin
      FCanvas.Ellipse(fx1, fy1, fx2, fy2);
    end;
  end;
end;

procedure TForm1.FormCreate(Sender: TObject);
begin
  jobList := TObjectList.Create;
end;

procedure TForm1.FormDestroy(Sender: TObject);
begin
  jobList.Free;
end;

procedure TForm1.BitBtn1Click(Sender: TObject);
begin
  jobList.Clear;

  jobList.Add( TDrawJob.CreateEx(Image1.Canvas, ejkRectangle, 100, 100, 200, 200 ) );
  jobList.Add( TDrawJob.CreateEx(Image1.Canvas, ejkEllipse, 100, 100, 200, 200 ) );
  jobList.Add( TDrawJob.CreateEx(Image1.Canvas, ejkLine, 100, 100, 200, 200 ) );
end;

procedure TForm1.BitBtn2Click(Sender: TObject);
var
  I : integer;
begin
  Memo1.Lines.Clear;
  for I := 0 to jobList.Count - 1 do
    Memo1.Lines.Add( TDrawJob( jobLIst[i]).asString );
end;

procedure TForm1.BitBtn3Click(Sender: TObject);
var
  i : integer;
begin
  for I := 0 to jobList.Count - 1 do
    TDrawJob(jobLIst[i]).Execute;
end;

end.

예제를 실행하면, 상단에 그리기 작업, 작업 리스트, 그리기 작업 실행 세개의 버튼이 나타난다. 첫번째 버튼은 가상의 그리기 작업들을 TList 객체에 보관하는 것이고, 두번째 버튼은 등록한 그리기 작업들 목록을 확인하는 것이고, 세번째는 그리기 작업들을 한꺼번에 실행하는 것이다.

닷넷의 모든 클래스들은 객체를 자동으로 문자열화하는 방법을 내장하고 있다. VCL에는 이 기능이 구현되어 있지 않다. 예제에서는 아주 간단한 방법으로 이를 구현했다. AsString 메서드를 추가한 것이다. 객체를 문자열화하는 방법을 미리 만들어 두면 디버깅시 여러모로 편리한 경우가 많다.

이 예제는 커맨드 패턴을 아주 간단하게 보여주기 위해, 소스 길이를 최대한 간략하게 했다. 이 때문에 리팩토링이 많이 필요한 미완성의 예제이다. 현재 이 소스는 몇가지 문제점이 있는데 대표적인 문제점들 몇개를 나열해 보자.

1. 다음 코드처럼 그리기 작업을 등록시키는 코드가 매우 복잡하다. 즉 인수를 매우 많이 전달해야 한다.

jobList.Add( TDrawJob.CreateEx(Image1.Canvas, ejkRectangle, 100, 100, 200, 200 ) );

2. 그리기 작업들을 보관하는 배열로 TLIst를 사용했는데 이는 다소 불편하다. 다음 코드처럼 형변환 코드가 자주 등장하기 때문이다.

for I := 0 to jobList.Count - 1 do
    Memo1.Lines.Add( TDrawJob( jobLIst[i]).asString );

3. 추후 새로운 그리기 작업 종류가 필요하면 소스 여러 곳을 수정해야 하며, 별로 안전하지 못하다. 예를 들어 Execute 메서드를 보면 그리기 작업의 종류에 따라서 실행할 코드를 결정하는 case문이 있는데, 객체지향에서 case문은 별로 바람직하지 못하다.

procedure TDrawJob.Execute;
begin
  case Kind of
    ejkLine :
    begin
      FCanvas.MoveTo(fx1, fy1);
      FCanvas.LineTo(fx2, fy2);
    end;
    ejkRectangle :
    begin
      FCanvas.Rectangle(fx1, fy1, fx2, fy2);
    end;
    ejkEllipse :
    begin
      FCanvas.Ellipse(fx1, fy1, fx2, fy2);
    end;
  end;
end;

이러한 문제점을 해결하기 위해서 리팩토링한 소스를 누군가 올려주기를 기대해 본다.

+ -

관련 글 리스트
97 실행 코드를 배열에 저장하고 나중에 한꺼번에 실행하기의 문제풀이 1탄 주정섭 5134 2006/05/02
Google
Copyright © 1999-2015, borlandforum.com. All right reserved.