데브맥스 프레임워크를 개발하고 있습니다. 데브맥스에서 사용하는 기술을 틈틈히 정리 및 공유하려 합니다.
(데브맥스 프레임워크에 대한 소개는 다음에 진행하겠습니다.)
이번 글에서는 팩토리 메소드 패턴(부모 클래스에 알려지지 않은 구체 클래스를 생성하는 패턴)과 클래스 타입을 활용해, 여러가지 객체 생성 시 참조 관계를 제거하는 방법을 소개합니다.
배경
1) 메인UI에서 여러가지 서브UI를 동적으로 생성하고 싶다.
2) 메인UI 소스에서 서버UI 소스 참조시 서브UI가 많아질 수록 메인UI 소스가 복잡해 진다.
과제
메인UI에서 서브UI Id로 서브UI 객체를 생성할 수 있어야 한다.
메인UI 소스에서 서브UI 소스를 직접 참조하지 않아야 한다.
서브UI가 늘어나도 메인UI 소스는 변경되지 않아야 한다.
방안
팩토리 메소드 패턴 활용
Class 타입을 이용해 확장성 제공
구현
아키텍처
위 그림과 같이 서브UI들은 ClassTypeFactory에 등록하고, 메인UI는 ClassTypeFactory를 참조해 객체를 생성하는 구조이다.
클래스 타입 지정
TViewItemClass = class of TControl; TViewItemClassInfo = record Id: string; ViewItemClass: TViewItemClass; constructor Create(AId: string; AItemClass: TViewItemClass); end;
Factory 클래스
type TViewItemClass = class of TControl; TViewItemClassInfo = record Id: string; ViewItemClass: TViewItemClass; constructor Create(AId: string; AItemClass: TViewItemClass); end; TViewItemFactory = class private class var FInstance: TViewItemFactory; private FViewItems: TDictionary; function GetList: TArray ; public constructor Create; destructor Destroy; override; procedure Regist(AId: string; AItemClass: TViewItemClass); function GetClass(AId: string): TViewItemClass; function CreateControl(AId: string): TControl; property List: TArray read GetList; class function Instance: TViewItemFactory; class procedure ReleaseInstance; end; { TViewItemFactory } class function TViewItemFactory.Instance: TViewItemFactory; begin if not Assigned(FInstance) then FInstance := Create; Result := FInstance; end; class procedure TViewItemFactory.ReleaseInstance; begin if Assigned(FInstance) then FInstance.Free; end; constructor TViewItemFactory.Create; begin FViewItems := TDictionary .Create; end; function TViewItemFactory.CreateControl(AId: string): TControl; var ItemClass: TViewItemClass; begin ItemClass := GetClass(AId); if not Assigned(ItemClass) then Exit(nil); Result := ItemClass.Create(nil); end; destructor TViewItemFactory.Destroy; begin FViewItems.Free; inherited; end; function TViewItemFactory.GetClass(AId: string): TViewItemClass; var Info: TViewItemClassInfo; begin Result := nil; if FViewItems.TryGetValue(AId, Info) then Result := Info.ViewItemClass; end; function TViewItemFactory.GetList: TArray ; begin Result := FViewItems.Values.ToArray; end; procedure TViewItemFactory.Regist(AId: string; AItemClass: TViewItemClass); begin FViewItems.Add(AId, TViewItemClassInfo.Create(AId, AItemClass)); end; initialization finalization TViewItemFactory.ReleaseInstance;
Factory 클래스가 싱글톤 패턴이 적용되어 약간 복잡하다
주요 메소드는 클래스 타입을 등록하는 Regist 메소드와 클래스 타입을 제공하는 GetClass 메소드이다.
서브UI에서 클래스 등록
type TFrame1 = class(TFrame) Label1: TLabel; Circle1: TCircle; private { Private declarations } public { Public declarations } end; implementation uses ClassTypeFactoryType; {$R *.fmx} initialization TViewItemFactory.Instance.Regist('프레임1', TFrame1);
서브UI에서는 ClassTypeFactory(TViewItemFactory)에 id('프레임1')와 클래스 타입(TFrame1)을 등록한다.
메인UI에서 서브UI 생성
uses ClassTypeFactoryType; {$R *.fmx} procedure TForm1.Button1Click(Sender: TObject); var Info: TViewItemClassInfo; Item: TListBoxItem; begin for Info in TViewItemFactory.Instance.List do begin Item := TListBoxItem.Create(ListBox1); Item.Parent := ListBox1; Item.Text := Info.Id; end; end; procedure TForm1.Button2Click(Sender: TObject); var Id: string; ItemClass: TViewItemClass; begin if Assigned(FActiveControl) then FActiveControl.Free; Id := ListBox1.Selected.Text; ItemClass := TViewItemFactory.Instance.GetClass(Id); FActiveControl := ItemClass.Create(Self); FActiveControl.Parent := Layout1; FActiveControl.Align := TAlignLayout.Client; end;
메인UI에서는 ClassTypeFactory에 등록된 서브UI를 참조할 수 있다.
메인UI는 Id 값으로 서브UI 클래스(TViewItemClass)를 가져와 동적으로 생성할 수 있다.
메인UI 소스에서는 서브UI 소스를 참조하지 않는다.(결합도를 낮출 수 있다.)
샘플코드
다운로드 : 01_ClassTypeFactory.zip
활용 : https://github.com/devgear/DevMax/blob/master/View/DevMax.View.Factory.pas
PS
짧은 시간을 할애해 정보를 자주 올리기 위해 두서없이 글을 작성했습니다. 혹시 이해되지 않는 내용이 있으면 댓글로 질문주시면 앞으로 보강하도록 하겠습니다.