이번 강좌에서는 다음 강좌를 위한 보충 지식으로, uses문의 Last-Win(마지막 놈이 장땡) 원칙을 설명하려 한다. 이 원칙은 대략 다음과 같다.
Last-Win원칙이란 ...
UnitC의 uses 문에 UnitA와 UnitB 순서로 나열되어 있고, UnitA와 UnitB에 동일한 명칭인 TX가 정의되어 있다면 UnitC의 TX는 유닛 B에서 정의한 것을 사용한다.
말이 좀 어려울수도 있는데, 사실 알고보면 간단하다. 다음과 같은 세 유닛이 있다고 가정하자.
------------------------------------
unit UnitA;
interface
type
TX = class
public
a : integer; <==멤버를 정수 타입으로 정의
end;
------------------------------------
unit UnitB;
interface
type
TX = class
public
a : String; <==멤버가 문자 타입으로 정의
end;
------------------------------------
unit UnitC;
interface
uses UnitA, UnitB;
procedure SomeMethod;
var
x : TX; <=== 이놈은 UnitB의 Tx 객체가 된다는 것이다.
begin
x := Tx.Create;
x.a := 100; // Compile Error. 왜냐면 UnitB의 Tx의 a 멤버 타입은 문자열이기 때문이다.
x.a := 'aaaaa'; // Compile OK!
end;
이 특성은 클래스가 아닌 함수나 프로시져, 전역 변수 모두 마찬가지다. 예를 들어 유닛 A,B,C가 다음과 같이 정의되어 있다고 가정한다면...
-------------------------------------------
unit UnitA;
interface
uses Dialogs;
procedure SomeFunc;
var
SomeVar : integer;
implementation
procedure SomeFunc;
begin
ShowMessage( 'UnitA.SomeFunc is called');
end;
end.
-------------------------------------------
unit UnitB;
interface
uses Dialogs;
procedure SomeFunc;
var
SomeVar : string;
implementation
procedure SomeFunc;
begin
ShowMessage( 'UnitB.SomeFunc is called');
end;
end.
-------------------------------------------------
unit UnitC;
//...중략
implementation
uses UnitA, UnitB;
procedure TForm7.FormCreate(Sender: TObject);
begin
SomeFunc; <== UnitB의 것을 호출한다.
SomeVar := 'aaaa'; <== UnitB의 전역 변수를 사용한다.
end;
UnitC에서의 SomeFunc 호출은 UnitB의 것을 사용하게 된다. last-win 원칙에 의거하여, uses 문에서 가장 나중에 나열된 유닛에서 정의된 명칭들이 우선하기 때문이다.
프로그래밍을 좀 배워본 사람이라면 동일한 지역성에서 동일한 명칭은 존재할 수 없다는 것을 알것이다. 따라서 같은 유닛에서는 동일한 지역성에 동일 명칭을 정의할 수 없다. 그러나, 정의된 유닛이 다를 경우 이는 허용된다. 결국, 오브젝트 파스칼에서 unit이란 C++이나 C#의 NameSpace와 거의 같은 역할을 한다. C++이나 C# 역시 NameSpace가 다르면 동일한 명칭 정의를 허용하듯이 말이다.
uses문의 Last-Win 현상은 어떤 경우에 사용해 먹을 수 있을까?
UnitA 에 SomeFunc가 정의되어 있는데, 대부분의 Unit은 SomeFunc가 UnitA에 원래 정의된 대로 사용해야 한다. 그러나 디버깅 혹은 특정 부서의 업무 요구 사항에 따라서 어떤 유닛에서는 SomeFunc가 다르게 동작되어야 한다고 가정하자. 이 경우 다른 모든 유닛의 SomeFunc 호출은 원래대로 유지하면서, 특정 유닛에서만 SomeFunc 동작을 다르게 하려면, 별도의 유닛인 UnitX 에 SomeFunc를 정의하고 이 유닛을 그 특정 유닛의 uses문 마지막에 두면 될 것이다. 그러면 Last-Win 원칙에 의거하여, 그 유닛에서만큼은 UnitX에서 정의한 SomeFunc가 호출될 것이다.
혹은 SomeFunc의 다른 동작이 필요한 유닛에서 스스로 SomeFunc를 새로 정의하면 된다. 동일 명칭이 있을 경우, 무조건 가까운 명칭이 우선한다는 특징을 이용한다는 것이다. 예를 들어 다음과 같이 UnitX를 작성하는 것이다.
----------------------------------------------
Unit UnitX; // SomeFunc 기능을 바꿔서 사용해야만 하는 유닛
interface
uses Dialogs, UnitA; // UnitA에 이미 SomeFunc가 정의되어 있다고 가정하자.
implementation
// 여기서 SomeFunc를 재정의함으로 인해서 이 SomeFunc가 가장 가까운 명칭이 된다.
// 따라서 이 시점 이후로 모든 SomeFunc의 호출은, UnitA의 것이 아닌 이 유닛자체의 SomeFunc고 대체 된다.
procedure SomeFunc;
begin
ShowMessage( 'UnitX.SomeFunc is called');
end;
procedure SomeFunc;
begin
ShowMessage( 'UnitA.SomeFunc is called');
end;
procedure SomeYYY;
begin
// 만일 unitA의 SomeFunc 호출이 이 유닛에 필요하다면 유닛명을 앞에 달면 된다.
UnitA.SomeFunc; //<== UnitA에서 정의된 SomeFunc를 호출한다.
// 여기서 UnitA은 객체명이 아니라, 범위 한정자의 역할을 한다.
// 즉 Scope Operator역할을 하는 것이다.
SomeFunc; //<== 이 유닛 자체, 즉 바로 위에서 정의된 SomeFunc를 호출한다.
end;
위 코드처럼 동일한 명칭이 다른 유닛에 정의되어 있을 경우, 그 유닛에서 정의한 명칭을 참조(호출)하려면, 명칭앞에 그 유닛명을 달면 된다.
이제, 오브젝트 파스칼에서 유닛명이 어떤 식으로 Namespace 역할을 하는지 알게 되었을 것이다. 콤포넌트를 살짜기 갈아치우는 방법은, 이 강좌에서 설명한 uses문의 LastWin 법칙과 imposter 클래스를 사용하는 것이다.
이에 대한 자세한 방법은 다음 파트에서 알아보도록 하자.
<<< to be continued >>>
|