객체지향에서 일종의 불문율같은 코딩 방식이 있습니다. 컴파일러가 반드시 그렇게 코딩해야 한다고 강제하지는 않으나, 반드시 그렇게 코딩하는 것이 옳다고 하는 방식입니다. 이는 오랜 동안 객체지향 기법을 연구한 끝에 여러 개발자들이 동의하는 사항들입니다. 그런 불문율 중에 몇가지를 논해 보려 합니다. ------------------------------------------------------------ 먼저 상위 클래스 메서드를 재정의할 때 사용하는 override문을 언제 사용하는가에 관한 것입니다. 이에 대한 룰은 다음과 같이 정리할 수 있습니다. 상위 클래스에서 virtual로 정의한 메서드를 하위 클래스에서 재정의할 때는 반드시 override 문을 사용하십시요. 이렇게 하지 않는 것은 virtual 메커니즘을 사용하는 근본 원칙을 위배하기 때문입니다. 따라서, 다음과 같은 코드로 작성하면, 상위 클래스의 virtual 메서드를 하위 클래스에서 일반 메서드로 바꾸는 것이 되기 때문에, 델파이 컴파일러는 경고를 날립니다. TBase = class(TObject) public procedure VirtualMethod; virtual; procedure StaticMethod; end; TDerived = class(TBase) public procedure VirtualMethod; // override; 오버라이드 문이 없으면 경고 발생 end; 델파이 컴파일러 경고문. [Warning]Method 'VirtualMethod' hides virtual method of base type 'TBase'; virtual메서드를 사용한다는 것은 하위 클래스에서 이 메서드를 재정의할 필요가 있다고 선언하는 것이기 때문에, override를 사용하지 않으면, 이 virtual 메커니즘이 무효화되어, 하위 클래스에서 VirtualMethod는 일반 메서드가 되어 버립니다. 이는 virtual 메서드가 실제 실행시 클래스에 따라서 호출된다는 메커니즘이 사라진다는 것이 됩니다. ------------------------------------------------------------ 그렇다면 상위 클래스의 일반 메서드를 하위 클래스에서 재정의하면 어떻게 될까요? 이 경우 델파이는 아무런 경고도 없지만, 이는 심각한 클래스 설계 오류일 수 있습니다. 상위 클래스와 하위 클래스에 동일한 메서드가 존재한다는 것은 사용법이 엄청 까다로운 클래스가 되기 때문입니다. // 위험함 클래스 설계 TBase = class(TObject) public procedure VirtualMethod; virtual; procedure StaticMethod; end; TDerived = class(TBase) public procedure StaticMethod; // override; 이 경우는 override문을 사용하면 명백히 컴파일 에러입니다. // override는 상위 클래스에서 이 메서드가 virtual일때만 사용가능합니다 end; ------------------------------------------------------------ 객체지향에서 virtual에 대한 또하나의 묵시적 코딩 룰은, 모든 파괴자 메서드는 반드시 virtual로 선언하라는 것입니다. 이는 대부분 객체들의 파괴자가, 실행시 클래스 타입에 따라서 올바른 파괴자가 호출되도록 보장하기 위함입니다. 여기서 그 이유를 설명하는 것은 복잡하므로 생략하지만, 이는 대부분 객체지향에 관한 책에서 필히 그렇게 할 것을 권하는 불문율입니다. 이 코딩 약속을 지켜야만 안전합니다. 이 때문에 파괴자를 재정의할 때 델파이 컴파일러는 강요하지 않지만, override를 반드시 사용해야만 합니다. 이 메커니즘을 깨면 엄청 골치 아픈 버그를 경험할 수 있으니 필히 지키길 권합니다. TDerived = class(TBase) public destructor Destroy; override; // 만일 override문을 지우면, 저 앞과 비슷한 경고 발생 end; TObject.Destroy는 이미 virtual로 정의되어 있습니다. ------------------------------------------------------------ 객체를 파괴할 때는 반드시 Free메서드나 FreeAndNil함수를 사용하십시요. Destroy메서드를 직접 부르는 것은 위험한 상황을 초래할 수 있습니다. 그 이유는 델파이 매뉴얼에 잘 기술되어 있습니다. ------------------------------------------------------------ 생성자와 파괴자를 재정의할 때, 사용자가 추가하는 코드의 위치가 서로 반대입니다. constructor TDerived.Create; begin // 간혹가다 사용자 코드를 여기에 둡니다. inherited; // 대부분의 경우 사용자 코드는 여기 둡니다. end; destructor TDerived.Destroy; begin // 파괴자인 경우, 사용자 코드는 여기 둡니다. inherited; // 파괴자에서 이부분에 코드를 둬야 할 경우는 아직 저는 찾지 못했습니다. end; inherited문은 상위 클래스에서 현재 메서드와 동일한 메서드를 호출하는 약어입니다. 생성자의 경우 사용자 코드를 필요에 따라서 inherited문을 아래위에 코드를 둘 수 있지만, 파괴자의 경우, 대부분 inherited 이전에 사용자 코드를 둡니다. 생성자는 자신의 최상위 클래스 생성자, 그 다음 상위 클래스 생성자 순으로 호출하지만, 파괴자는 이와 역순으로 상위 클래스의 파괴자를 호출하기 때문입니다. 따라서 명백한 이유가 없다면 생성자와 파괴자에 코드를 추가할 때는 반드시 이 원칙을 따르십시요 ---------------------------------------------------------------- C++과는 달리 델파이에는 클래스 레퍼런스 혹은 메타 클래스라는 개념이 있습니다. 이는 클래스 자체를, 즉 클래스 타입 자체를 함수나 메서드에 인수로 전달할 수 있는 기능입니다. 이는 C++의 템플리트 기능과 유사한 면이 많지만, 제 개인적으로는 C++ 템플리트보다 월등히 편하다고 생각합니다. 어쨋든 이 클래스 레퍼런스를 제대로 사용하려면, TObject의 생성자와 TComponent의 생성자의 모양이 좀 다르다는 점에 유의해야 합니다. TObject의 생성자 constructor Create; TComponent의 생성자 constructor Create(AOwner: TComponent); virtual; TObject와 달리 TComponent의 생성자는 virtual로 선언되어 있다는 점에 유의해야 합니다. 따라서 TComponent 이하의 클래스에서 상속받아 만든 클래스는 생성자를 재정의 할 때 반드시 override문을 사용해야 합니다. 이를 잊어먹으면 후일 그 클래스의 클래스 레퍼런스를 사용할 때 원치 않는 결과가 발생합니다. TMyComponent = class(TComponent) public constructor Create(Owner:TComponent); override; // 반드시 override문을 사용 하십시요. destructor Destroy; override; // 파괴자역시 반드시 override문을 사용하십시요. end; ------------------------------------------------------------------ virtual 메서드는 객체지향에서 매우 중요하며너 막강한 기능입니다. Tags: 윈도우즈
|