델파이 리팩토링 연습 문제
(이글은 자게란에 올린 글이지만, 강좌란에 두는 것이 좋다고 생각되어 옮깁니다.)
밑의 함수는 지인으로 부터 받은 소스에 포함되어 있던 함수중 하나이다.
이 함수의 역할은 컴에 설치된 문서(워드, 엑셀, 아래아한글 등등) 타입들을 알아내고, 이 정보들을 바탕으로 FileOpen 다이얼로그의 filter 속성으로 사용할 문자열을 얻어 내는 것이다. 아래는 그 소스인데, 소스 구조는 매우 간단하다.
function GetFileFilter:String;
var
checkVal:OleVariant;
begin
Result := 'Image All(*.jpg;*.jpeg;*.gif;*.png;*.bmp;*.pcx;*.tif;*.tiff;*.psd;*.pdd)|*.jpg;*.jpeg;*.gif;*.png;*.bmp;*.pcx;*.tif;*.tiff;*.psd;*.pdd|';
try
checkVal := CreateComObject(CLASS_HwpCtrl);
checkVal := Unassigned;
Result := Result + 'HWP Document(*.hwp)|*.hwp|';
except
RemoveFileDocKind(dkHwp);
end;
try
checkVal := CreateOleObject('WORD.Application');
checkVal := Unassigned;
Result := Result + 'MS-Word(*.doc)|*.doc|';
except
RemoveFileDocKind(dkMsWord);
end;
try
checkVal := CreateOleObject('Excel.Application');
checkVal := Unassigned;
Result := Result + 'MS-Excel(*.xls)|*.xls|';
except
RemoveFileDocKind(dkMsExcel);
end;
try
checkVal := CreateOleObject('PowerPoint.Application');
checkVal := Unassigned;
Result := Result + 'PowerPoint(*.ppt)|*.ppt|';
except
RemoveFileDocKind(dkMsPPt);
end;
try
checkVal := CreateComObject(CLASS_IF_);
checkVal := Unassigned;
Result := Result + 'HunminWord Document(*.gul)|*.gul|';
except
RemoveFileDocKind(dkHun);
end;
Result := Result + 'Adobo PDF File(*.pdf)|*.pdf|PostScript(*.ps)|*.ps|Encapsulated PostScript(*.eps)|*.eps|';
end;
그런데 이 함수는 몇가지 문제점이 있다.
첫번째로 모양이 매우 비슷한 try...except 구문이 여러번 반복된다는 것이다. 반복되는 구조의 코드는 제거함이 옳을 것이다.
두번째로 후일 새로운 문서 타입을 추가해야할 때, 이 try except 구문이 또 반복 추가되고, 새 문서 타입 추가하기가 그리 쉽지 않다.
이 두문제를 해결할 수 있도록 이 코드를 정리해 보자. 리팩토링이란 기존 코드의 기능은 그대로 유지하면서도, 더욱 깔끔하고 정갈한 구조를 가지는 코드로 변경하는 작업이다. 이 코드를 깔끔하게 정리해 주시라.
-------------------------------------------------------------------------------------------------------
아래는 이 글에 대한 해답으로 올려준 댓글들입니다.
참여해 주신 분들께 대단히 감사드립니다.
--------------------------------------------------------------------------------
윤승환 님의 리팩토링
--------------------------------------------------------------------------------
function GetFileFilter():String;
function AddWordFiler(classid:TGUID;filter:string;docktype:TDockType;var filterlist:string) :string; overload;
var
checkVal :OleVariant;
begin
try
checkVal := CreateComObject(classid);
checkVal := Unassigned;
Result := filter;
except
RemoveFileDocKind(docktype);
Result :='';
end;
end;
function AddWordFiler(classname:string;filter:string;docktype:TDockType;var filterlist:string) :string; overload;
begin
result :=AddWordFiler(ProgIDToClassID(classname),filter,docktype,filterlist);
end;
begin
result :='Image All(*.jpg;*.jpeg;*.gif;*.png;*.bmp;*.pcx;*.tif;*.tiff;*.psd;*.pdd)|*.jpg;*.jpeg;*.gif;*.png;*.bmp;*.pcx;*.tif;*.tiff;*.psd;*.pdd|';
AddWordFiler(CLASS_HwpCtrl,'HWP Document(*.hwp)|*.hwp|',dkHwp,Result);
AddWordFiler('WORD.Application','MS-Word(*.doc)|*.doc|',dkMsWord,Result);
AddWordFiler('Excel.Application','MS-Excel(*.xls)|*.xls|',dkMsExcel,Result);
AddWordFiler('PowerPoint.Application','PowerPoint(*.ppt)|*.ppt|',dkMsPPt,Result);
AddWordFiler(CLASS_IF_,'HunminWord Document(*.gul)|*.gul|',dkHun,Result);
result :=result + 'Adobo PDF File(*.pdf)|*.pdf|PostScript(*.ps)|*.ps|Encapsulated PostScript(*.eps)|*.eps|';
end;
---------------------------------------------------------------------------------
이경환.단디
---------------------------------------------------------------------------------
function GetFileFilter:String;
type
TDocType = Record
docId : String;
filter : string;
dockKind : integer;
IsOle: Boolean;
end;
const
docTypes : array[0..2] of TDocType = (
(docId:'WORD.Application'; filter:'MS-Word(*.doc)|*.doc|'; dockKind:dkWord; IsOle:true),
(docId:'Excel.Application'; filter:'MS-Excel(*.xls)|*.xls|'; dockKind:dkExcel; IsOle:true),
(docId:'{26319A34-0ADB-4F04-8EFD-4E6679644879}'; filter:'HWP Document(*.hwp)|*.hwp|'; dockKind:dkHwp; IsOle:false)
);
function checkFileServer(docId: string; docKind: integer; IsOle: Boolean): boolean;
var
checkVal: OleVariant;
begin
result:= false;
try
try
if IsOle then
checkVal:= CreateComObject(docId)
else
checkVal:= CreateOleObject(StringToGuid(docId));
result:= true;
except
RemoveFileDocKind(docKind);
end;
finally
checkVal:= Unassigned;
end;
end;
begin
Result := 'Image All(*.jpg;*.jpeg;*.gif;*.png;*.bmp;*.pcx;*.tif;*.tiff;*.psd;*.pdd)|*.jpg;*.jpeg;*.gif;*.png;*.bmp;*.pcx;*.tif;*.tiff;*.psd;*.pdd|';
for I := Low(docTypes) to High(docTypes) do
begin
if checkFileServer(docTypes[i].oleType, docTypes[i].docKind, docTypes[i].IsOle) then
begin
Result := Result + docTypes[i].filter;
end;
end;
Result := Result + 'Adobo PDF File(*.pdf)|*.pdf|PostScript(*.ps)|*.ps|Encapsulated PostScript(*.eps)|*.eps|';
end;
---------------------------------------------------------------------------------
미노님의 방법
---------------------------------------------------------------------------------
type
TDocumentType = (dtHWP, dtWord, dtPowerpoint, dtExcel, dtHunminWord);
TDocumentFilter = class
private
FDocumentType: TDocumentType;
public
class function DocumentFilter(Filter: TDocumentType): string;
function GetDocumentFilter: string;
property DocumentType:TDocumentType read FDocumentType write FDocumentType;
end;
const
DocumentType: array [1..5] of TDocumentType = (dtHWP, dtWord, dtPowerpoint, dtExcel, dtHunminWord);
implementation
{ TDocumentFilter }
class function TDocumentFilter.DocumentFilter(Filter: TDocumentType): string;
begin
case Filter of
dtHWP: Result:= 'HWP Document(*.hwp)|*.hwp|';
dtWord: Result:= 'MS-Word(*.doc)|*.doc|';
dtExcel: Result:= 'MS-Excel(*.xls)|*.xls|';
dtPowerpoint: Result:= 'PowerPoint(*.ppt)|*.ppt|';
dtHunminWord: Result:= 'HunminWord Document(*.gul)|*.gul|';
else result:= '';
end;
end;
function TDocumentFilter.GetDocumentFilter: string;
begin
Result:= DocumentFilter(FDocumentType);
end;
function GetFileFilter:String;
var
I: Integer;
begin
Result:= 'Image All(*.jpg;*.jpeg;*.gif;*.png;*.bmp;*.pcx;*.tif;*.tiff;*.psd;*.pdd)|*.jpg;*.jpeg;*.gif;*.png;*.bmp;*.pcx;*.tif;*.tiff;*.psd;*.pdd|';
for I := Low(DocumentType) to High(DocumentType) do
Result:= Result + TDocumentFilter.DocumentFilter(DocumentType[I]);
Result:= Result + 'Adobo PDF File(*.pdf)|*.pdf|PostScript(*.ps)|*.ps|Encapsulated PostScript(*.eps)|*.eps|';
end;
---------------------------------------------------------------------------------
주정섭의 방법
---------------------------------------------------------------------------------
// 컴파일 되게 하기 위해서...다음 함수를 구라로 정의
procedure RemoveFileDocKind(aValue: Integer);
begin
end;
// 컴파일 되게 하기 위해서 다음 상수들을 구라로 정의
const dkHwp = 100;
const dkWord = 101;
const dkExcel = 102;
// 리팩토링한 GetFileFilter 함수
function GetFileFilter:String;
type
TDocType = Record
comObject : TGUID;
className : String;
filter : string;
dockKind : integer;
end;
const
noGuid = '{00000000-0000-0000-0000-000000000000}';
docTypes : array[0..2] of TDocType = (
(comObject:noguid; className:'WORD.Application'; filter:'MS-Word(*.doc)|*.doc|'; dockKind:dkWord),
(comObject:noguid; className:'Excel.Application'; filter:'MS-Excel(*.xls)|*.xls|'; dockKind:dkExcel),
(comObject:'{26319A34-0ADB-4F04-8EFD-4E6679644879}'; filter:'HWP Document(*.hwp)|*.hwp|'; dockKind:dkHwp )
);
var
checkVal:OleVariant;
I: Integer;
begin
Result := 'Image All(*.jpg;*.jpeg;*.gif;*.png;*.bmp;*.pcx;*.tif;*.tiff;*.psd;*.pdd)|*.jpg;*.jpeg;*.gif;*.png;*.bmp;*.pcx;*.tif;*.tiff;*.psd;*.pdd|';
for I := Low(docTypes) to High(docTypes) do
begin
try
if GuidToString(docTypes[i].comObject) = noGuid then
checkVal := CreateOleObject(docTypes[i].ClassName)
else
checkVal := CreateComObject(docTypes[i].comObject);
checkVal := Unassigned;
Result := Result + docTypes[i].filter;
except
RemoveFileDocKind(docTypes[i].dockKind);
end;
end;
Result := Result + 'Adobo PDF File(*.pdf)|*.pdf|PostScript(*.ps)|*.ps|Encapsulated PostScript(*.eps)|*.eps|';
end;
-------------------------------------------------------------------------------------
사악신 님이 올린 리팩토링
-------------------------------------------------------------------------------------
1. 사용하고자 하는 유닛에서 다음처럼 호출
:: 초기화시 사용해도 되고 필요시 동적으로 달고 싶은 놈만 다는 형태로 사용...
procedure TFormFileFilter.ButtonOpenClick(Sender: TObject);
var
Filters : TStringList;
begin
Filters := TStringList.Create;
try
Filters := TDecoratorImageAll.Create( Filters );
Filters := TDecoratorHWP.Create( Filters );
Filters := TDecoratorExcel.Create( Filters );
Filters := TDecoratorPDF.Create( Filters );
OpenDialog.Filter := Filters.Text;
if OpenDialog.Execute then
begin
//
end;
finally
Filters.Free;
end;
end;
2. 데코레이터 소스
:: BDS 에서 지원하는 투게더가 좀 미흡한 관계로(오류가 많습니다. ㅡㅡ) 생산성은 조금 떨어지지만,
잘 활용하면 코드량에 비해서 그렇게 많은 시간이 들어가지 않습니다. ^^
델파이 하위 버전과의 호환성을 위하여 추가된 문법들은 정리하여 제거하였습니다.
해당 문서의 검사루틴은 생략했습니다. 각자 적당한 방법으로 채우거나 구조를 활용하면 되지 않을까 싶어서...
아무튼 구조만 잡아봤습니다.
unit FileFilter.Classes.Documents; // BDS 이전 Delphi 7 이하 버전에서는 네임스페이스를 지원하지 않습니다.
interface
uses
Classes;
type
TDecoratorDoc = class(TStringList)
protected
FComponent: TStringList;
function GetDoc: String; virtual; abstract;
public
constructor Create(ADecorateMe: TStringList);
destructor Destroy; override;
function GetTextStr: String; override;
end;
TDecoratorImageAll = class(TDecoratorDoc)
protected
function GetDoc: String; override;
end;
TDecoratorHWP = class(TDecoratorDoc)
protected
function GetDoc: String; override;
end;
TDecoratorExcel = class(TDecoratorDoc)
protected
function GetDoc: String; override;
end;
TDecoratorWord = class(TDecoratorDoc)
protected
function GetDoc: String; override;
end;
TDecoratorPPT = class(TDecoratorDoc)
protected
function GetDoc: String; override;
end;
TDecoratorPDF = class(TDecoratorDoc)
protected
function GetDoc: String; override;
end;
TDecoratorPS = class(TDecoratorDoc)
protected
function GetDoc: String; override;
end;
TDecoratorEPS = class(TDecoratorDoc)
protected
function GetDoc: String; override;
end;
implementation
{ TDecoratorDoc }
constructor TDecoratorDoc.Create(ADecorateMe: TStringList);
begin
inherited Create;
FComponent := ADecorateMe;
end;
destructor TDecoratorDoc.Destroy;
begin
FComponent.Free;
FComponent := nil;
inherited;
end;
function TDecoratorDoc.GetTextStr: String;
var
Temp: String;
begin
Temp := inherited GetTextStr;
Temp := Temp + FComponent.Text + GetDoc;
Result := Temp;
end;
{ TDecoratorHWP }
function TDecoratorHWP.GetDoc: String;
begin
Result := 'HWP Document(*.hwp)|*.hwp|';
try
// 검사루틴...
except
Result := '';
end;
end;
{ TDecoratorExcel }
function TDecoratorExcel.GetDoc: String;
begin
Result := 'MS-Excel(*.xls)|*.xls|';
try
// 검사루틴...
except
Result := '';
end;
end;
{ TDecoratorImageAll }
function TDecoratorImageAll.GetDoc: String;
begin
Result := 'Image All(*.jpg;*.jpeg;*.gif;*.png;*.bmp;*.pcx;*.tif;*.tiff;*.psd;*.pdd)|*.jpg;*.jpeg;*.gif;*.png;*.bmp;*.pcx;*.tif;*.tiff;*.psd;*.pdd|';
end;
{ TDecoratorPPT }
function TDecoratorPPT.GetDoc: String;
begin
Result := 'PowerPoint(*.ppt)|*.ppt|';
try
// 검사루틴...
except
Result := '';
end;
end;
{ TDecoratorWord }
function TDecoratorWord.GetDoc: String;
begin
Result := 'MS-Word(*.doc)|*.doc|';
try
// 검사루틴...
except
Result := '';
end;
end;
{ TDecoratorPDF }
function TDecoratorPDF.GetDoc: String;
begin
Result := 'Adobo PDF File(*.pdf)|*.pdf|';
try
// 검사루틴...
except
Result := '';
end;
end;
{ TDecoratorPS }
function TDecoratorPS.GetDoc: String;
begin
Result := 'PostScript(*.ps)|*.ps|';
try
// 검사루틴...
except
Result := '';
end;
end;
{ TDecoratorEPS }
function TDecoratorEPS.GetDoc: String;
begin
Result := 'Encapsulated PostScript(*.eps)|*.eps|';
try
// 검사루틴...
except
Result := '';
end;
end;
end.
|