자주 바뀌는 개발 요구사항에 대비해 최소의 비용을 들여 전략적으로 코드가 유연성을 가질 수 있도록 설계하는 기법
(코드의 유연성이란 말 안에는 기능의 구현없이 완성된 모듈을 재빠르게 가져다 쓸수 있도록 하는 전략도 포함)
Ex;차를 생산하는데
차의 프레임은 고정되어 있지만 차에 부착되는 옵션은 차량 주문자의 요구에 의해 수시로 바뀐다. 이럴 때 제조에 필요한 주문자의 요구사항에서 벗어난 모든 옵션부품이 든 거대한 상자를 프레임 옆에 두고 차를 조립하는 것은 공간적인 낭비와 기타비용이 많이 들 뿐 아니라 수많은 부품중에 필요한 부품을 찾는데(가독성 떨어짐) 시간을 소모해야 하기 때문에 생산성 또한 떨어진다.
이런 경우를 프로그램에서는 간단한 기능 하나만 필요한데 불필요하게 Super Class를 상속받아 쓴다라고 표현한다. (닭 잡는데 소 잡는 칼 쓴다는 말과 같이..)
경우에 OOP에서 상속이 이에 해당된다.
상속은 경우에 따라 비용이 많이 들 수 있으며, 단일 상속밖에 되지 않는 치명적인 단점 때문에 코드의 유연성이 급격하게 떨어진다.
이를 해결하기 위해서는 다중상속이 가능한 Interface 얘기를 빼놓을 수 없는데, 대부분의 강좌들이 그 부분을 간과하여 설명하고 있다.
Strategy 패턴은 완성된 기능들을 가진 모듈들을 어떤 구현도 없이 재빠르게 가져다 쓸 수 있도록 설계하여야 Strategy 전략 패턴이 가진 이름값을 제대로 할 수 있다.
기능 모두를 직접 구현하여야 한다면 이 난해한 패턴을 굳이 가져다 쓰는 이점이 무엇일까? 이는 Strategy 패턴에 대한 잘못된 오해이다.
TClassA = class
필요한 기능1;
필요한 기능2;
불필요한 기능;
end;
TClassB = class(TClassA)
필요한 기능1;
필요한 기능2;
불필요한 기능;
....
end;
이 경우 클래스 상속에서 불필요한 기능을 없애려면 불필요한 기능을 override 할 수밖에 없다. 하지만 이런 과정은 상속받아가는 하위 클래스의 종류가 많을 경우 관리가 무척 힘들어지게 된다.
그럼, override 하지않고 불필요한 기능을 제거할 수 있을까?
이를 위해선 다음과 같이 다중상속 구조가 성립되어야 한다.
TClassB = class(필요한 기능1을 가진 클래스, 필요한 기능2를 가진 클래스)
필요한 기능1;
필요한 기능2;
end;
하지만 델파이에서는 다중상속이 지원되지 않기 때문에 위 형태로는 클래스를 설계할 수 없다.
이 문제를 해결하려면 Interface를 사용해야 한다.
TClassB = class( 필요한 기능1을 가진 Interface, 필요한 기능2를 가진 Interface )
필요한 기능1;
필요한 기능2;
end;
필요한 기능 1, 2는 Interface의 메소드이다.
위 같은 형태로 해서 Interface로부터 꼭 필요한 기능들만 상속받아 쓸 수 있다.
이 부분에서 아까전 비유를 든 자동차 옵션부품 얘기를 이해할 수 있다!
(이렇게 되면 제조에 꼭 필요한 부품만 옆에 두고 조립한다고 할 수 있다)
실질적인 사용 예를 들면
TClassB = class( TInterfacedObejct, ISpeak, IWork )
Speak; //필요한 기능1 - ISpeak 의 메소드;
Work; //필요한 기능2 - IWork 의 메소드;
end;
Interface 객체를 생성하기 위해 TInterfaceObject 클래스를 상속받고
ISpeak와 IWork Interface를 다중상속 받았다.
Speak 기능은 경우에 따라 동적으로 기능이 변경될 수 있도록 만들어야 Strategy 패턴을 제대로 구현했다고 할 수 있다.
다시말해, 사용자가 Speak를 호출하더라도 어떤 경우는 한국말을 해야 할 때도 있고 경우에 따라서는 영어를 말해야 할 때도 있다는 말이다.
한국어로 말하기와 영어로 말하기 기능 구현 코드가 짧다면 모르겠지만 코드가 엄청나게 길다고 가정해 본다면 외부 Interface 메소드 규격 Speak를 지키면서, 내부 기능은 사용자의 요구사항에 따라 동적으로 다른 메서드에서 작동될 수 있도록 하는 방법을 찾아야한다.
이에 대한 솔루션은 다음과 같이 추상화 패턴을 도입하는 것이다.
ISpeak = interface
speak;
end;
TSpeakKorean = (TInterfacedObject, ISpeak)
speak;
end;
TSpeakEnglish = (TInterfacedObject, ISpeak)
speak;
end;
*TSpeakKorean과 TSpeakEnglish는 ISpeak를 상속받았기 때문에 상호교환 가능
이런 추상화 기법은 클래스에서도 가능하지만, 다중상속을 위해 Interface를 쓴 이상 위처럼 Interface로 해결해야 한다.
1) 다중상속을 통한 모듈을 꽂아 쓰는 방식을 채택함으로 클래스 확장성 확보
2) Interface 메서드의 추상화를 통한 메서드의 동적기능 선택 유연성 확보
마지막으로 꼭 필요한 건, 완성된 모듈을 어떻게 꽂아 쓰는가이다.
우리가 Interface라는 말을 들을 땐 정의만 하고 구현할 수 없다라고 배웠다.
그래서 위 같은 방법으로 프로그램을 짜더라도 기능구현은 결국 최종 클래스의 사용자의 몫이 아닌가라고 생각할 수 있다.
하지만 여기서 알아야 할 사실은
DirectX 는 Interface 를 이용한 COM 기술로 만들어 져 있다.
Interface 를 썻으니 DirectX 를 당신이 모조리 구현하여 써야 하는가?
Interface 의 최종목적은 개발언어에 상관없이 이미 완성된 기능을 쉽게 가져다 쓰기 위함에 있는 것이지,
정의만 하자고 만들어 둔 개념이 절대 아니다.
정의만 하자고 그걸 만들었으면 생산성이 없어도 너무 없게 된다.
unit uHuman;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls;
// *************** Interface Type 定義 ***************
type
ISpeak = interface
procedure Speak;
end;
TKorean = class( TInterfacedObject, ISpeak)
procedure Speak;
end;
TEnglish = class( TInterfacedObject, ISpeak)
procedure Speak;
end;
IWork = interface
procedure Work;
end;
TSing = class( TInterfacedObject, IWork)
procedure Work;
end;
TCook = class( TInterfacedObject, IWork)
procedure Work;
end;
TMan = class(TInterfacedObject)
public
procedure live;
procedure die;
end;
THumanBasic = class(TMan, ISpeak)
private
FSpeak : ISpeak;
public
constructor Create( SpeakKind : ISpeak );
procedure Speak;
end;
THumanExpand = class(TMan, ISpeak, IWork)
private
FSpeak : ISpeak;
FWork : IWork;
public
constructor Create( SpeakKind : ISpeak; WorkKind : IWork );
procedure Speak;
procedure Work;
end;
// *************** Function 定義 ***************
implementation
{ TMan }
procedure TMan.live;
begin
ShowMessage('Live');
end;
procedure TMan.die;
begin
ShowMessage('Die');
end;
{ THuman }
constructor THumanBasic.Create(SpeakKind: ISpeak);
begin
FSpeak := SpeakKind;
end;
procedure THumanBasic.Speak;
begin
FSpeak.Speak();
end;
{ THumanExpand }
constructor THumanExpand.Create(SpeakKind: ISpeak; WorkKind: IWork);
begin
FSpeak := SpeakKind;
FWork := WorkKind;
end;
procedure THumanExpand.Speak;
begin
FSpeak.Speak();
end;
procedure THumanExpand.Work;
begin
FWork.Work();
end;
{ Korean }
procedure TKorean.Speak;
begin
ShowMessage('K');
end;
{ American }
procedure TEnglish.Speak;
begin
ShowMessage('E');
end;
{ TSing }
procedure TSing.Work;
begin
ShowMessage('Sing');
end;
{ TSing }
procedure TCook.Work;
begin
ShowMessage('I can Cook.');
end;
end.
unit uMain;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls;
type
TForm1 = class(TForm)
GroupBox1: TGroupBox;
Button1: TButton;
Button2: TButton;
GroupBox2: TGroupBox;
Button3: TButton;
Button4: TButton;
procedure Button1Click(Sender: TObject);
procedure Button2Click(Sender: TObject);
procedure Button3Click(Sender: TObject);
procedure Button4Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
uses
uHuman;
{$R *.dfm}
{Application}
procedure TForm1.Button1Click(Sender: TObject);
var
aMan : THumanBasic;
begin
aMan := THumanBasic.Create( TKorean.Create );
aMan.live;
aMan.Speak;
aMan.die;
end;
procedure TForm1.Button2Click(Sender: TObject);
var
aMan : THumanBasic;
begin
aMan := THumanBasic.Create( TEnglish.Create );
aMan.live;
aMan.Speak;
aMan.die;
end;
procedure TForm1.Button3Click(Sender: TObject);
var
aMan : THumanExpand;
begin
aMan := THumanExpand.Create( TKorean.Create, TSing.Create );
aMan.live;
aMan.Speak;
aMan.Work;
aMan.die;
end;
procedure TForm1.Button4Click(Sender: TObject);
var
aMan : THumanExpand;
begin
aMan := THumanExpand.Create( TEnglish.Create, TCook.Create );
aMan.live;
aMan.Speak;
aMan.Work;
aMan.die;
end;
end.