티스토리 툴바


'소프트웨어 공학/디자인패턴'에 해당되는 글 2건

  1. 2012/01/31 State Pattern을 이용한 미디어 서버 구축 과정
  2. 2010/08/26 양치기 소년 패턴 (Proxy Pattern의 부분적 활용)
소스가 복잡한 편이라 예제로서는 부적합 할 수 있으나, 현재 작업 중인 과정을 잠시 설명하려고 합니다.  중단했던 미디어 서버 개발이 모두 완료되면 다시 정리해서 올리도록 하겠습니다.

우선 어제 새벽에 그린 상태도는  [그림 1]과 같습니다.  기존의 UML 툴이 제공하는 상태변화의 선행처리와 후행처리 표시가 복잡해 보여서, 네모 박스로 표시했습니다.  문제는 상태가 완전히 변한 후에 처리되느냐, 전에 처리되느냐를 알 수가 없다는 것 입니다.  나중에 좀 더 고민하여 표기법을 정리해보려고 합니다.

[그림 1] 미디어 서버의 클라이언트 모듈의 일부에 대한 상태도

[그림 1]은 TCP 소켓을 이용해서 미디어 서버로부터 데이터를 스트림으로 받는 TFFTCPStream라는 클래스의 상태도입니다.   TFFTCPStream는 아래와 같은 조건에서 개발 중 입니다.
  • TFFTCPStream 은 서버에 있는 동영상 파일의 정보 및 데이터를 수신 받아서 버퍼에 저장한다.  이 버퍼는 미디어 플레이어 클래스에서 사용되어 재생된다.
  • 안드로이드 플랫폼에서 사용 될 것이다.  다만, 디버깅 효율을 높이기 위해서 델파이로 원도우용 클라이언트를 작성하여 테스트를 진행 할 것이다.
  • 스마트 폰의 특성 상 접속이 자주 끊어질 수 있다.  따라서, 접속이 끊어지면 기존의 세션을 복구해야 한다.
  • 재생 위치의 빠른 이동을 위해서 서버에서는 인덱싱 정보를 캐싱하고 있다.
  • 기타 등등 

[소스 1] 패킷의 종류
type
  TPacketType = (
    // 공통
    ptNone, ptPing,

    // Server Received
    ptOptions, ptOpen, ptReopen, ptAsk, ptClose, ptLogin, ptLogout,

    // Client Received
    ptPackage, ptOkOpen, ptErOpen, ptOkReopen, ptErReopen,
    ptOkLogin, ptErLogin, ptOkLogout, ptErLogout
  );
  PPacketType = ^TPacketType;
[소스 1]은 통신 할 때 사용하는 패킷의 종류들 입니다.  대부분 이름을 보면 금새 무엇을 하는 넘인지 알 수 있을 것이라 생각합니다.  ptReopen은 접속이 끊어졌다가 다시 연결되었을 때, 기존의 세션을 복구하기 위한 패킷입니다.  ptAsk는 인덱스 위치에 해당하는 미디어 데이터를 요청합니다.


[소스 2]  주요 클래스의 인터페이스
type
  TFFTCPStream = class (TComponent)
  private
  public
    function OpenFile(AHost:string; APort:integer; AFileName:string):boolean;
    procedure CloseFile;
  published
    property IsOpened : boolean read FIsOpened;
    property OnFileOpened : TFileOpenedEvent read FOnFileOpened write FOnFileOpened;
    property OnFileOpenError : TNotifyEvent read FOnFileOpenError write FOnFileOpenError;
    property OnFileClosed : TNotifyEvent read FOnFileClosed write FOnFileClosed;
  end;

  TState = class
  private
    FFFTCPStream:TFFTCPStream;
  public
    constructor Create(AFFTCPStream:TFFTCPStream); reintroduce; virtual;

    procedure ActionIn(AOldState:TObject); virtual;
    procedure ActionOut(ANewState:TObject); virtual;

    function OpenFile(AHost:string; APort:integer; AFileName:string):boolean; virtual;
    procedure CloseFile; virtual;

    procedure on_Timer; virtual;
    procedure on_Disconnected; virtual;

    procedure pt_Package(AData:pointer; ASize:integer); virtual;
    procedure pt_OkOpen(AData:pointer; ASize:integer); virtual;
    procedure pt_ErOpen(AData:pointer; ASize:integer); virtual;
    procedure pt_OkReopen(AData:pointer; ASize:integer); virtual;
    procedure pt_ErReopen(AData:pointer; ASize:integer); virtual;
  end;
TSate는 각 상태에 대한 클래스의 추상화된 부모 클래스입니다.

20: 상태가 변화되서 자신이 선택되었을 경우 실행됩니다.  자신이 선택되었을 때 선행처리를 합니다.
21: 상태가 변화되서 다른 상태가 선택되었을 경우 실행됩니다.  다른 상태로 전이될 때 후행처리를 합니다.

23-33: [그림 1]의 상태도에서 화살표에 그려진 프로세스들을 모두 표기한 것 입니다.


[소스 3] 디버깅을 위한 코드
{ TState }

procedure TState.CloseFile;
begin
  {$IFDEF DEBUG}
    OutputDebugString(PChar(Format('%s.CloseFile: ', [Self.ClassName])));
  {$ENDIF}
end;

{ TStateConnected }

procedure TStateConnected.CloseFile;
begin
  {$IFDEF DEBUG}
    inherited;
  {$ENDIF}

  FFFTCPStream.close_File;
  FFFTCPStream.SetState(FFFTCPStream.FStateClosed);
end;
부모 클래스인 TState에는 모든 메소드마다 디버깅 정보를 출력하도록 되어 있습니다.  이를 통해서 실제 동작 할 때 어떤 순서로 동작하는 지, 그리고 [그림 1]의 상태도의 조건대로 프로세스가 진행된느 지를 쉽게 확인 할 수가 있습니다.  자식 클래스에서는 inherited 를 실행하여 자신이 동작 할 때마다 디버그 정보를 [그림 2]와 같이 출력하도록 합니다.

[그림 2] 디버깅 정보 확인


[소스 4] FFTCPStream.pas의 전체 소스
unit FFTCPStream;

interface

uses
  FFServerUtils, RyuMpeg, RyuSocketClient, ThreadRepeater,
  Windows, Classes, SysUtils, SyncObjs;

const
  _TimerInterval = 1 * 1000;  // 1초 마다

type
  TFileOpenedEvent = procedure (Sender:TObject; AMediaInfo:TMediaInfo) of object;

  TFFTCPStream = class (TComponent)
  private
    FFileName : string;
    FOldTick : Cardinal;
    FCS : TCriticalSection;
    procedure close_File;
    procedure fire_OnFileOpened(AMediaInfo:TMediaInfo);
    procedure fire_OnFileOpenError;
    procedure fire_OnFileClosed;
  private
    FSocket : TRyuSocketClient;
    procedure on_Disconnected(Sender:TObject);
    procedure on_Received(Sender:TObject; AData:pointer; ASize:integer);
  private
    procedure send_Text(APacketType:TPacketType; AText:AnsiString);
    procedure send_Open;
    procedure send_Reopen;
    procedure send_Close;
    procedure send_Ask(AIndex:integer);
  private
    FTimer : TThreadRepeater;
    procedure on_Timer(Sender:TObject);
  private
    FState : TObject;
    FStateClosed, FStateConnected, FStateOpened, FStateDisconnected, FStateReconnected : TObject;
    procedure SetState(AState:TObject);
  private
    FOnFileClosed: TNotifyEvent;
    FOnFileOpenError: TNotifyEvent;
    FOnFileOpened: TFileOpenedEvent;
    FIsOpened: boolean;
  public
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;

    function OpenFile(AHost:string; APort:integer; AFileName:string):boolean;
    procedure CloseFile;
  published
    property IsOpened : boolean read FIsOpened;
    property OnFileOpened : TFileOpenedEvent read FOnFileOpened write FOnFileOpened;
    property OnFileOpenError : TNotifyEvent read FOnFileOpenError write FOnFileOpenError;
    property OnFileClosed : TNotifyEvent read FOnFileClosed write FOnFileClosed;
  end;

implementation

type
  TState = class
  private
    FFFTCPStream:TFFTCPStream;
  public
    constructor Create(AFFTCPStream:TFFTCPStream); reintroduce; virtual;

    procedure ActionIn(AOldState:TObject); virtual;
    procedure ActionOut(ANewState:TObject); virtual;

    function OpenFile(AHost:string; APort:integer; AFileName:string):boolean; virtual;
    procedure CloseFile; virtual;

    procedure on_Timer; virtual;
    procedure on_Disconnected; virtual;

    procedure pt_Package(AData:pointer; ASize:integer); virtual;
    procedure pt_OkOpen(AData:pointer; ASize:integer); virtual;
    procedure pt_ErOpen(AData:pointer; ASize:integer); virtual;
    procedure pt_OkReopen(AData:pointer; ASize:integer); virtual;
    procedure pt_ErReopen(AData:pointer; ASize:integer); virtual;
  end;

  TStateClosed = class (TState)
  private
  public
    function OpenFile(AHost:string; APort:integer; AFileName:string):boolean; override;
  end;

  TStateConnected = class (TState)
  private
  public
    function OpenFile(AHost:string; APort:integer; AFileName:string):boolean; override;
    procedure CloseFile; override;

    procedure on_Timer; override;
    procedure on_Disconnected; override;

    procedure pt_OkOpen(AData:pointer; ASize:integer); override;
    procedure pt_ErOpen(AData:pointer; ASize:integer); override;
  end;

  TStateOpened = class (TState)
  private
  public
    function OpenFile(AHost:string; APort:integer; AFileName:string):boolean; override;
    procedure CloseFile; override;

    procedure on_Timer; override;
    procedure on_Disconnected; override;

    procedure pt_Package(AData:pointer; ASize:integer); override;
  end;

  TStateDisconnected = class (TState)
  private
  public
    function OpenFile(AHost:string; APort:integer; AFileName:string):boolean; override;
    procedure CloseFile; override;

    procedure on_Timer; override;
  end;

  TStateReconnected = class (TState)
  private
  public
    function OpenFile(AHost:string; APort:integer; AFileName:string):boolean; override;
    procedure CloseFile; override;

    procedure on_Timer; override;

    procedure pt_OkReopen(AData:pointer; ASize:integer); override;
    procedure pt_ErReopen(AData:pointer; ASize:integer); override;
  end;

{ TFFTCPStream }

procedure TFFTCPStream.CloseFile;
begin
  TState(FState).CloseFile;
end;

procedure TFFTCPStream.close_File;
begin
  SetState(FStateClosed);

  FTimer.Stop;
  send_Close;

  FIsOpened := false;
  fire_OnFileClosed;
end;

constructor TFFTCPStream.Create(AOwner: TComponent);
begin
  inherited;

  FFileName := '';
  FIsOpened := false;

  FSocket := TRyuSocketClient.Create(Self);
  FSocket.OnDisconnected := on_Disconnected;
  FSocket.OnReceived := on_Received;

  FCS := TCriticalSection.Create;

  FStateClosed  := TStateClosed.Create(Self);
  FStateConnected  := TStateConnected.Create(Self);
  FStateOpened  := TStateOpened.Create(Self);
  FStateDisconnected  := TStateDisconnected.Create(Self);
  FStateReconnected := TStateReconnected.Create(Self);

  FState := FStateClosed;

  FTimer := TThreadRepeater.Create(Self);
end;

destructor TFFTCPStream.Destroy;
begin
  CloseFile;

  FreeAndNil(FSocket);
  FreeAndNil(FStateClosed);
  FreeAndNil(FStateConnected);
  FreeAndNil(FStateOpened);
  FreeAndNil(FStateDisconnected);
  FreeAndNil(FStateReconnected);
  FreeAndNil(FTimer);
  FreeAndNil(FCS);

  inherited;
end;

procedure TFFTCPStream.fire_OnFileClosed;
begin
  if Assigned(FOnFileClosed) then FOnFileClosed(Self);
end;

procedure TFFTCPStream.fire_OnFileOpened(AMediaInfo:TMediaInfo);
begin
  // TODO : Player.Open(AMediaInfo);
  if Assigned(FOnFileOpened) then FOnFileOpened(Self, AMediaInfo);
end;

procedure TFFTCPStream.fire_OnFileOpenError;
begin
  if Assigned(FOnFileOpenError) then FOnFileOpenError(Self);
end;

procedure TFFTCPStream.on_Disconnected(Sender: TObject);
begin
  TState(FState).on_Disconnected;
end;

procedure TFFTCPStream.on_Received(Sender: TObject; AData: pointer;
  ASize: integer);
var
  pPacketType : ^TPacketType absolute AData;
begin
  case pPacketType^ of
    ptPackage:  TState(FState).pt_Package(AData, ASize);
    ptOkOpen:   TState(FState).pt_OkOpen(AData, ASize);
    ptErOpen:   TState(FState).pt_ErOpen(AData, ASize);
    ptOkReopen: TState(FState).pt_OkReopen(AData, ASize);
    ptErReopen: TState(FState).pt_ErReopen(AData, ASize);
  end;
end;

procedure TFFTCPStream.on_Timer(Sender: TObject);
var
  Tick : Cardinal;
  Temp : integer;
begin
  Tick := GetTickCount;

  FCS.Enter;
  try
    if Tick > FOldTick then Temp := Tick - FOldTick
    else Temp := 0;

    if Temp >= _TimerInterval then begin
      FOldTick := Tick;
      TState(FState).on_Timer;
    end;
  finally
    FCS.Leave;
  end;

  Sleep(1);
end;

function TFFTCPStream.OpenFile(AHost: string; APort: integer;
  AFileName: string): boolean;
begin
  FIsOpened := false;
  FFileName := AFileName;

  Result := TState(FState).OpenFile(AHost, APort, AFileName);
  if Result then FTimer.Execute(on_Timer);
end;

procedure TFFTCPStream.send_Ask(AIndex: integer);
var
  Packet : TPacketAsk;
begin
  Packet.PacketType := ptAsk;
  Packet.Index := AIndex;

  FSocket.Send(@Packet, SizeOf(Packet));
end;

procedure TFFTCPStream.send_Close;
var
  PacketType : TPacketType;
begin
  PacketType := ptClose;
  FSocket.Send(@PacketType, SizeOf(PacketType));
end;

procedure TFFTCPStream.send_Open;
begin
  send_Text(ptOpen, FFileName);
end;

procedure TFFTCPStream.send_Reopen;
begin
  send_Text(ptReopen, FFileName);
end;

procedure TFFTCPStream.send_Text(APacketType: TPacketType; AText: AnsiString);
var
  pPacket : ^TPacket;
  iPacketSize : integer;
begin
  iPacketSize := SizeOf(TPacketType) + Length(AText) + 1;

  GetMem(pPacket, iPacketSize);
  try
    FillChar(pPacket^, iPacketSize, 0);

    pPacket^.PacketType := APacketType;

    if Length(AText) > 0 then Move(AText[1], pPacket^.DataStart, Length(AText));

    FSocket.SendNow(pPacket, iPacketSize);
  finally
    FreeMem(pPacket);
  end;
end;

procedure TFFTCPStream.SetState(AState: TObject);
var
  OldState, NewState : TObject;
begin
  FCS.Enter;
  try
    FOldTick := GetTickCount;

    OldState := FState;
    NewState := AState;

    TState(OldState).ActionOut(NewState);
    FState := NewState;
    TState(NewState).ActionIn(OldState);
  finally
    FCS.Leave;
  end;
end;

{ TState }

procedure TState.ActionIn(AOldState: TObject);
begin
  {$IFDEF DEBUG}
    OutputDebugString(PChar(Format('%s.ActionIn: OldState = %s', [Self.ClassName, AOldState.ClassName])));
  {$ENDIF}
end;

procedure TState.ActionOut(ANewState: TObject);
begin
  {$IFDEF DEBUG}
    OutputDebugString(PChar(Format('%s.ActionOut: NewState = %s', [Self.ClassName, ANewState.ClassName])));
  {$ENDIF}
end;

procedure TState.CloseFile;
begin
  {$IFDEF DEBUG}
    OutputDebugString(PChar(Format('%s.CloseFile: ', [Self.ClassName])));
  {$ENDIF}
end;

constructor TState.Create(AFFTCPStream: TFFTCPStream);
begin
  inherited Create;

  FFFTCPStream := AFFTCPStream;
end;

procedure TState.on_Disconnected;
begin
  {$IFDEF DEBUG}
    OutputDebugString(PChar(Format('%s.on_Disconnected: ', [Self.ClassName])));
  {$ENDIF}
end;

procedure TState.on_Timer;
begin
  {$IFDEF DEBUG}
    OutputDebugString(PChar(Format('%s.on_Timer: ', [Self.ClassName])));
  {$ENDIF}
end;

function TState.OpenFile(AHost: string; APort: integer;
  AFileName: string): boolean;
begin
  {$IFDEF DEBUG}
    OutputDebugString(PChar(Format('%s.OpenFile: %s, %d, %s', [Self.ClassName, AHost, APort, AFileName])));
  {$ENDIF}

  Result := false;
end;

procedure TState.pt_ErOpen(AData: pointer; ASize: integer);
begin
  {$IFDEF DEBUG}
    OutputDebugString(PChar(Format('%s.pt_ErOpen: ', [Self.ClassName])));
  {$ENDIF}
end;

procedure TState.pt_ErReopen(AData: pointer; ASize: integer);
begin
  {$IFDEF DEBUG}
    OutputDebugString(PChar(Format('%s.pt_ErReopen: ', [Self.ClassName])));
  {$ENDIF}
end;

procedure TState.pt_OkOpen(AData: pointer; ASize: integer);
begin
  {$IFDEF DEBUG}
    OutputDebugString(PChar(Format('%s.pt_OkOpen: ', [Self.ClassName])));
  {$ENDIF}
end;

procedure TState.pt_OkReopen(AData: pointer; ASize: integer);
begin
  {$IFDEF DEBUG}
    OutputDebugString(PChar(Format('%s.pt_OkReopen: ', [Self.ClassName])));
  {$ENDIF}
end;

procedure TState.pt_Package(AData: pointer; ASize: integer);
begin
  {$IFDEF DEBUG}
    OutputDebugString(PChar(Format('%s.pt_Package: ', [Self.ClassName])));
  {$ENDIF}
end;

{ TStateClosed }

function TStateClosed.OpenFile(AHost: string; APort: integer;
  AFileName: string): boolean;
begin
  {$IFDEF DEBUG}
    inherited;
  {$ENDIF}

  FFFTCPStream.FSocket.Host := AHost;
  FFFTCPStream.FSocket.Port := APort;
  FFFTCPStream.FSocket.Connect;

  Result := FFFTCPStream.FSocket.Connected;

  if Result then begin
    FFFTCPStream.SetState(FFFTCPStream.FStateConnected);
    FFFTCPStream.send_Open;
  end;
end;

{ TStateConnected }

procedure TStateConnected.CloseFile;
begin
  {$IFDEF DEBUG}
    inherited;
  {$ENDIF}

  FFFTCPStream.close_File;
  FFFTCPStream.SetState(FFFTCPStream.FStateClosed);
end;

procedure TStateConnected.on_Disconnected;
begin
  {$IFDEF DEBUG}
    inherited;
  {$ENDIF}

  FFFTCPStream.SetState(FFFTCPStream.FStateDisconnected);
end;

procedure TStateConnected.on_Timer;
begin
  {$IFDEF DEBUG}
    inherited;
  {$ENDIF}

  FFFTCPStream.send_Open;
end;

function TStateConnected.OpenFile(AHost: string; APort: integer;
  AFileName: string): boolean;
begin
  {$IFDEF DEBUG}
    inherited;
  {$ENDIF}

  raise Exception.Create(ClassName + '.OpenFile: ');
end;

procedure TStateConnected.pt_ErOpen(AData: pointer; ASize: integer);
begin
  {$IFDEF DEBUG}
    inherited;
  {$ENDIF}

  FFFTCPStream.fire_OnFileOpenError;
  FFFTCPStream.SetState(FFFTCPStream.FStateClosed);
end;

procedure TStateConnected.pt_OkOpen(AData: pointer; ASize: integer);
var
  pPacket : ^TPacketOkOpen absolute AData;
begin
  {$IFDEF DEBUG}
    inherited;
  {$ENDIF}

  FFFTCPStream.FIsOpened := true;

  // TODO : Buffer.Clear;

  FFFTCPStream.SetState(FFFTCPStream.FStateOpened);
  FFFTCPStream.send_Ask(0);
  FFFTCPStream.fire_OnFileOpened(pPacket^.MediaInfo);
end;

{ TStateOpened }

procedure TStateOpened.CloseFile;
begin
  {$IFDEF DEBUG}
    inherited;
  {$ENDIF}

  FFFTCPStream.close_File;
  FFFTCPStream.SetState(FFFTCPStream.FStateClosed);
end;

procedure TStateOpened.on_Disconnected;
begin
  {$IFDEF DEBUG}
    inherited;
  {$ENDIF}

  FFFTCPStream.SetState(FFFTCPStream.FStateDisconnected);
end;

procedure TStateOpened.on_Timer;
begin
  {$IFDEF DEBUG}
    inherited;
  {$ENDIF}

  // TODO : FFFTCPStream.send_Ask(Buffer.NextIndex);
end;

function TStateOpened.OpenFile(AHost: string; APort: integer;
  AFileName: string): boolean;
begin
  {$IFDEF DEBUG}
    inherited;
  {$ENDIF}

  raise Exception.Create(ClassName + '.OpenFile: ');
end;

procedure TStateOpened.pt_Package(AData: pointer; ASize: integer);
begin
  {$IFDEF DEBUG}
    inherited;
  {$ENDIF}

  // TODO : Buffer.Add();
end;

{ TStateDisconnected }

procedure TStateDisconnected.CloseFile;
begin
  {$IFDEF DEBUG}
    inherited;
  {$ENDIF}

  FFFTCPStream.close_File;
  FFFTCPStream.SetState(FFFTCPStream.FStateClosed);
end;

procedure TStateDisconnected.on_Timer;
begin
  {$IFDEF DEBUG}
    inherited;
  {$ENDIF}

  FFFTCPStream.FSocket.Connect;

  if FFFTCPStream.FSocket.Connected then begin
    if FFFTCPStream.FIsOpened then begin
      FFFTCPStream.SetState(FFFTCPStream.FStateReconnected);
      FFFTCPStream.send_Reopen;
    end else begin
      FFFTCPStream.SetState(FFFTCPStream.FStateConnected);
      FFFTCPStream.send_Open;
    end;
  end;
end;

function TStateDisconnected.OpenFile(AHost: string; APort: integer;
  AFileName: string): boolean;
begin
  {$IFDEF DEBUG}
    inherited;
  {$ENDIF}

  raise Exception.Create(ClassName + '.OpenFile: ');
end;

{ TStateReconnected }

procedure TStateReconnected.CloseFile;
begin
  {$IFDEF DEBUG}
    inherited;
  {$ENDIF}

  FFFTCPStream.close_File;
  FFFTCPStream.SetState(FFFTCPStream.FStateClosed);
end;

procedure TStateReconnected.on_Timer;
begin
  {$IFDEF DEBUG}
    inherited;
  {$ENDIF}

  FFFTCPStream.send_Reopen;
end;

function TStateReconnected.OpenFile(AHost: string; APort: integer;
  AFileName: string): boolean;
begin
  {$IFDEF DEBUG}
    inherited;
  {$ENDIF}

  raise Exception.Create(ClassName + '.OpenFile: ');
end;

procedure TStateReconnected.pt_ErReopen(AData: pointer; ASize: integer);
begin
  {$IFDEF DEBUG}
    inherited;
  {$ENDIF}

  FFFTCPStream.close_File;
end;

procedure TStateReconnected.pt_OkReopen(AData: pointer; ASize: integer);
begin
  {$IFDEF DEBUG}
    inherited;
  {$ENDIF}

  FFFTCPStream.SetState(FFFTCPStream.FStateOpened);
end;

end.




 
저작자 표시 비영리 변경 금지
Posted by 류종택
객체와 객체 사이의 의존성을 분석하고 설계를 진행할 때,
가끔, 서버 역할을 하는 객체의 존재 여부가 불확실 할 때가 있다.

다른 객체에게 메시지를 보내는 쪽을 클라이언트,
다른 객체로 부터 메시지를 수신하는 쪽을 서버로 간주한다.

이러한 경우에는 논리적으로 설계하는데 다소 어려움을 주게 된다.

가장 쉽게 떠오르는 해결책은 [그림 1]과 같다.

[그림 1] 객체의 존재여부를 확인하고 필요할 때마다 생성한다.

[그림 1]의 문제는 클라이언트가 너무 많은 책임을 가지고 있다는 것이다.

객체지향적 프로그래밍을 제대로 이해했다면,
상대가 어떤 상태인지 알필요 없이, 내가 원하는 것을 알려주기만 하면 되는 것이다.

만약,
"경우에 따라서 생성되어야 할 서버 객체가 다른 타입이어야 한다"와 같은 요구사항이 발생하면,
[그림 1]의 경우는 유지/보수의 비용이 상승하게 된다.

해당 서버 객체를 이용하는 객체들이 다수라면 상황은 더욱 복잡해지며,
멀티 쓰레드 상황이거나, 생성 삭제가 빈번하다면 아주 끔직한 상황을 맞이하게 된다.

늑대(객체)의 존재가 불확실하여,
늑대를 잡으러 온 클라이언트 객체들의 분노가 시스템을 마비시킬 수도 있다.

좀더 간명한(?) 방법은 [그림 2]와 같이 프록시(대변인) 객체를 이용하는 것이다.

객체지향적 프로그래밍은 이해를 돕고 유지/보수를 간명하게 해줄 지는 몰라도,
그것을 만들어 내는 데까지의 과정은 간명하다고 표현하기 어렵다.

[그림 2] 프록시 객체가 서버의 존재를 보증한다.

결과적으로 클라이언트는 서버의 존재 여부에 대한 것을 프록시로부터 보증받게 되어,
단순히 프록시 객체의 메소드를 호출하고 결과를 받아서 처리하면 된다.

서버의 존재 여부와 요구사항 변화로 인한 문제 또는 멀티 스레드의 처리 등은,
프록시에게 위임되어 캡슐화 처리 된다.
Posted by 류종택