객체지향 언어로서의 오브젝트 파스칼 마지막편
■상속성과 델파이 폼
새로운 프로젝트를 생성하면, 델파이는 새로운 폼을 보여준다.
코드 에디터의 새로운 프로젝트의 내용을 살펴 보면,
델파이가 폼에 대한 새로운 객체 클래스를 선언하고
새로운 폼 객체를 생성하는 코드를 만들어 낸다는 것을 알 수 있다.
그러면 초기에 생성되는 델파이의 코드를 살펴 보자.
unit Unit1;
interface
uses Windows, Classes, Graphics, Forms, Controls, ... ;
type
TForm1 = class(TForm) {폼에 대한 새로운 클래스 선언}
private
{ Private declarations }
public
{ Public declarations }
end; {클래스 선언부는 여기서 종료된다.}
var
Form1: TForm1;
implementation {구현 부분의 시작}
{$R *.DFM}
end. {구현 부분과 유닛의 끝}
새로운 객체 데이터 형인 TForm1은
TForm에서 상속받는다는 것을 알 수 있다.
객체에는 데이터 필드와 메소드를
가진다는 것은 이미 설명한 바 있다.
그런데, TForm1에는 아직 메소드나
데이터 필드를 지정하지 않았다.
다만 TForm 클래스에서 상속받은 메소드와
프로퍼티를 가지게 될 것이다.
개발자는 TForm1의 선언 부분에 마음대로
메소드나 프로퍼티 등을 추가할 수 있다.
또한, 컴포넌트를 폼에 추가하는 동작에 의해
델파이가 메소드나 데이터 필드를 추가해 준다.
그렇지만, 이 어플리케이션을 실행시켜 보면
간단한 폼이 생성되는 것을 볼 수 있다.
이것이 의미하는 것은 무엇인가 ?
즉, 기본적으로 TForm 클래스의
모든 메소드와 기능을 상속받아서 사용할 수 있다는 것이다.
변수 선언부에서는 새로운 변수인
Form1을 TForm1으로 선언한다.
이렇게 선언함으로써 TForm1 클래스의
인스턴스가 될 수 있다.
클래스의 인스턴스는 여러 개
생성될 수 있다는 것은 이미 알고 있을 텐데,
만약 TForm1 클래스를 여러 인스턴스로 생성하면
그것이 바로 MDI(Multiple Document Interface)
어플리케이션이 되는 것이다.
그러면, 폼에 버튼을 하나 추가하고
그 버튼의 OnClick 이벤트 핸들러를 다음과 같이 작성하자.
procedure TForm1.Button1Click(Sender: TObject);
begin
Form1.Color := clGreen;
end;
이렇게 이벤트 핸들러를 작성하고 나면,
폼의 유닛의 코드는 다음과 같이 바뀌어 있을 것이다.
unit Unit1;
interface
uses Windows, Classes, Graphics, Forms, Controls, ...;
type
TForm1 = class(TForm)
Button1: TButton; {새로운 데이터 필드}
procedure Button1Click(Sender: TObject); {새로운 메소드}
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form1: TForm1;
... (후략)
TForm1의 선언부에 새로운 Button1 이라는
필드가 추가 되었음을 알 수 있다.
이렇게 새로운 컴포넌트를 폼에 추가할 때마다
새로운 필드가 type 선언부에 추가된다.
또한, 델파이에서 작성하는
모든 이벤트 핸들러는 폼 객체의 메소드로 선언된다.
TForm1에는 이제 Button1Click이라는
새로운 메소드 프로시저가 추가되었다.
여기서 이런 동작들을 OOP의 개념으로 생각해보자.
델파이의 폼 디자이너는 과연 무엇인가 ?
결국 델파이의 폼 디자이너는
TForm이라는 클래스를 상속받은
새로운 형태의 클래스를 쉽게
만들어주는 일종의 위저드인 셈이다.
이제 상속의 의미를 이해하기 쉬운 비유를 들어 생각해 보자.
앞에서 처음 어플리케이션이 생성되었을 때의
폼은 기본 옵션으로된 자동차를 한 대 구입한 것으로 가정하자.
기본 옵션의 자동차는 자동차 메이커에서
항상 정해진 방식대로 만들어지기 때문에
가장 기본적인 기능만을 가지고 달릴 수 있을 것이다.
그렇지만, 이 자동차를 구입한 사람이 에어컨도 달고,
파워 핸들과 에어백 등의 여러가지 옵션을 장착할 수 있다.
이를 위해서 자동차를 구입한 사람은 자동차 전체를
새로 만들 필요는 없는 것이다.
즉, 이를 상속을 통해 설명하자면 기본적인 자동차를
상속받은 사용자가 자식 클래스 자동차에
새로운 옵션들을 추가한 것이다.
마찬가지로 델파이의 폼은 상속성을 설명할 때 가장 알기 쉽고,
전형적인 방법을 보여준다고 말할 수 있다.
■ 클래스의 범위 (scope)
클래스의 멤버들은 서로 다른 범위를 가질 수 있다.
이러한 범위를 나타내는 지시어로는
private, protected, public, published의 4가지가 있다.
참고로 오브젝트 파스칼에서는 유닛도 범위를 결정하는데 한 몫을 한다.
비록 private로 선언되었더라도 같은 유닛에 있으면 모두 접근이 가능하다.
■ private
다른 유닛에 있는 경우 private 섹션에 선언된 멤버에는 접근할 수 없다.
다른 사용자가 접근할 필요가 없는 멤버들은 여기에 선언한다.
■ protected
이 클래스에서 상속받은 클래스에서만 접근할 수 있는 멤버들을 여기에 선언한다.
즉, 현재의 클래스를 상속받아서 새로운 클래스를 만들 때 수정이 필요하다면
개발자에게 접근이 가능해야 하지만,
일반적으로 사용할 때에는 접근할 수 없도록 할 때 사용된다.
■ public, published
다른 클래스에서 제한 없이 사용될 수 있는 멤버들을 여기에 선언한다.
published는 오브젝트 인스펙터에서도 볼 수 있는 멤버들을 선언할 때 사용한다.
published로 선언하면 멤버에 대한 RTTI(runtime type information)가 생성되며,
이를 이용해서 다른 프로그램들이 런타임에서
객체에 대한 필드, 메소드, 프로퍼티에 접근하게 된다.
델파이는 RTTI를 이용하여 오브젝트 인스펙터에 프로퍼티를 보여주며,
폼 파일에 프로퍼티의 값을 저장하고 불러올 수 있다.
Published 프로퍼티에 사용할 수 있는 데이터 형은 서수형과
문자열, 클래스와 메소드 포인터형, 세트형, 실수형 등을 사용할 수 있다.
배열은 사용할 수 없다.
published 멤버가 있는 대부분의 클래스는
TPersistent 클래스에서 상속받는 것이 보통이다.
범위에 대한 지시어가 없을 때에는 일단 public으로 간주한다.
그렇지만, 프로그래밍을 할 때에는
꼭 이러한 범위를 지정해주는 것이 좋은 버릇이다.
■ 메소드 (Method)
객체의 동작은 메소드에 의해 정의된다.
메소드는 프로시저나 함수와 비슷하지만
지정한 클래스와 그의 파생 클래스 만의 객체를 위해
정의된다는 점이 다르다.
메소드에는 보이지 않는 파라미터로서 Self라는
객체 자신의 참조자가 전달된다.
메소드는 또한 클래스 메소드로서 선언될 수 있는데,
이런 경우에는 클래스 참조로는 호출될 수 있으나
객체 참조로서는 호출되지 않는다. 객체가 없으므로 Self 파라미터도 없다.
■ 메소드의 종류
메소드에도 그 동작방식과 용도에 따라 여러 가지 종류가 있다.
다음에 이들 각각의 특징에 대해 설명하였다.
1. 정적 메소드 (Static method): 지시어 static;
아무런 지시어가 없을 때에는 디폴트로 정적 메소드로 간주된다.
컴파일 할 당시에 메소드가 위치한
메모리 주소가 확정되는 메소드이므로,
그만큼 실행속도가 빠르지만
상속을 받은 클래스에서 메소드를 새롭게 정의하면,
그 메소드만(같은 이름의 경우) 사용이 가능하므로 융통성이 적다.
2. 가상 메소드 (Virtual mehtod): 지시어 virtual;
가상 메소드는 실행 시에 late binding이라는 과정을 통해
실제로 호출될 메모리 주소가 결정된다.
내부적으로 가상 메소드가 참조되면 변수에 의해 참조되는
객체의 실제 클래스 형이 사용되는데,
이 작업이 가상함수 테이블(virtual method table(VMT), vtable)에
기록되어 있는 주소를 참조하여 이루어진다.
그러므로, 실제 참조되는 클래스 형에 따라서
여러가지 메소드가 호출될 수 있는 ‘다형성’이 구현될 수 있다.
3. 동적 메소드 (Dynamic method): 지시어 dynamic;
기본적인 사용법이나 목적은 가상 메소드와 동일하지만,
내부적인 처리 방법에 약간의 차이가 있다.
가상 메소드가 내부적으로 VMT를 이용해서
메모리 주소를 참조하는데 비해
동적 메소드는 메소드를 지정하는 코드를 이용해서
메모리 주소를 찾게 된다.
그렇기 때문에 가상 메소드처럼 테이블을 직접 참조하는 방법보다
다소 느리게 동작하지만 메모리는 덜 사용하게 된다.
4. 추상 메소드 (Abstract method): 지시어 abstract;
일반적으로 메소드를 클래스에 선언할 때,
컴파일러는 메소드 프로시저가 유닛의
어느 부분인가 구현되어 있을 것으로 간주하게 된다.
그런데, 추상 메소드로 선언하면 컴파일러는 구현 부분이
자손 클래스에 있을 것으로 생각하고 이를 검사하지 않는다.
그런데, 만약 자손 클래스에서 이를 구현하지 않아서
추상 메소드가 호출되면 치명적인 에러가 발생하게 되므로 주의하기 바란다.
■ 메소드 오버라이드 (method override)
override 지시어는 가상 또는 동적 메소드를 재정의할 때 사용된다.
재정의하는 방법은 다음과 같다.
type
TMyParent = class
procedure AMethod; virtual;
end;
TMyClass = class(TMyParent)
procedure AMethod; override;
end;
■ 클래스 메소드 (Class method)
보통 메소드는 클래스의 인스턴스에 대한 행동을 정의한다.
그런데, 어떤 때에는 클래스 자체에 대한 메소드가 있으면 할 경우가 있다.
이럴 때에는 클래스 메소드를 정의해서 사용한다.
클래스 메소드는 객체가 생성되지 않아도 사용할 수 있다.
클래스 메소드를 선언하려면 메소드 정의 부분에서
class 키워드를 앞에 붙여주면 된다.
■ 메소드 오버로딩
델파이 4에서는 객체 들이 같은 이름을 가진
여러 개의 메소드를 가질 수 있다.
이를 메소드 오버로딩이라고 하는데,
같은 이름을 가진 메소드 들은
argumets의 type signature를 가지고 서로를 구별한다.
오버로드된 메소드는 키워드 오버로드로 표시된다.
그렇기 때문에, 객체 들은 다음과 같이 다른 두 개의 생성자를 가질 수 있다.
constructor Create(AOwner: TComponent); overload; override;
constructor Create(AOwner: TComponent; Text: string); overload;
전역 함수와 프로시저역시 오버로드가 가능하다.
델파이 3 까지만 해도 메소드 오버로딩을 지원하지 않았기 때문에,
생성자의 이름을 다르게 했어야 했다.
예를 들어 모든 윈도우 컨트롤은
Create, CreateParented의 2개의 생성자를
공통적으로 가지고 있었다.
델파이 4 부터는 Create라는 메소드 이름을
가진 여러 생성자를 가질 수 있다.
오브젝트 파스칼의 이전 버전에서는 같은 이름의 메소드를
선언할 경우 조상 클래스의 메소드는 사용되지 않았다.
예를 들어, Create 메소드를 Owner 파라미터를 넘겨주지 않고
호출할 경우 과거에는 TObject에 선언된 생성자를 호출하지 않고
컴파일 에러를 발생시켰다.
메소드 오버로딩으로 이러한 문제들이
다소 변경되었는데, 다음의 코드를 살펴보자.
type
A = class
public
procedure p(I: Integer); virtual;
end;
B = class(A)
public
procedure p(S: string);
end;
var
aB: B;
begin
aB.p('one'); { works }
aB.p(1); { compile error! }
end;
여기에서 변경된 메소드의 파라미터를 대입할 경우에는 동작하지만,
원본 클래스의 메소드는 동작하지 않는다.
이를 해결하기 위해서는 overload 지시어를 사용하면 된다.
다음의 코드를 살펴보자.
type
A = class
public
procedure p(I: Integer); overload; virtual;
end;
B = class(A)
public
procedure p(S: string); overload;
end;
var
aB: B;
begin
aB.p('one'); { works }
aB.p(1); { now this one works too! }
end;
이 경우에는 동작하지만, 컴파일러가 경고를 한다.
이를 없애기 위해서는 상속받은 메소드에
다음과 같이 reintroduce 키워드를 지정하면 된다.
type
A = class
public
procedure p(I: Integer); overload; virtual;
end;
B = class(A)
public
procedure p(I: Integer; S: string); reintroduce; overload;
end;
■ 동적 바인딩(Dynamic binding)과 다형성(Polymorphism)
파스칼의 함수와 프로시저는 기본적으로
정적 바인딩(static binding)을 이용하고 있다.
이것은 메소드 호출이 컴파일러나 링커에 의해 해석되며,
컴파일러나 링커는 이 호출문을 그 함수나 프로시저가 존재하는
특정 메모리 위치의 호출로 바꾸어 놓는다는 의미이다.
오브젝트 파스칼을 비롯한 객체지향 언어는
이와 다른 형태의 동적 바인딩을 지원한다.
이 경우에는 메소드의 실제 메모리 주소가 실행 중에 결정되는 것이다.
이런 특성을 이용해 다형성을 지원할 수 있게 되는데,
다형성이란 어떤 메소드의 호출문을 작성하고 그것을 변수에 대입해도,
어느 메소드가 호출될지는 그 변수에 관계된
객체의 데이터 형에 따라 달라지는 것이다.
다시 말해, 주어진 메소드에 대해 다수의 버전이 있을 수 있고,
그래서 하나의 메소드가 이러한 버전들 각각을 가리킬 수 있다.
끝!
'Delphi > 문법' 카테고리의 다른 글
유한 루테인지아잔틴 캐시워크 돈버는퀴즈 정답은? 'ㅇㅇmg' (0) | 2021.06.10 |
---|---|
델파이 집합, 열거형, 열거형루프, 객체형 (0) | 2021.06.10 |
객체지향 언어로서의 오브젝트 파스칼 3편 (0) | 2021.06.08 |
대웅제약 밀크씨슬 에너씨슬 캐시워크 돈버는퀴즈 정답은? 'ㅇㄴㅆㅅ' (0) | 2021.06.07 |
객체지향 언어로서의 오브젝트 파스칼 2편 (0) | 2021.06.07 |
댓글