클래스(Class)
Posted on 2008/08/05 12:40
Filed Under Delphi
클래스는 그것을 특징짓는 속성과 그 행동 양식을 나타내는 코드를 함께 가지고 있는 사용자 정의 데이터형이라 말할 수 있습니다. 클래스는 객체 지향 프로그램이의 뼈대이며, 클래스를 통해 상속, 정보 숨김, 다형성 등 모?자동차가 있는데, 이 자동차를 컴퓨터로 표현할려면 어떻게 해야 할까요? 차라는 특성을 나타내는 차종, 무게, 배기량, 제작회사 등과 같은 것이 먼저 떠오를 것입니다. 이런 것들은 레코드 형을 통해서 구현할 수 있습니다. 즉 아래와 같은 레코드 형이 될 것입니다.
TCar=record
CarName:string;
Weight:longint;
Exhaust:longint;
Manufacture:string;
end;
그러나 이러한 것 만으로 차라는 물건을 제대로 표현할 수는 없습니다. 여기에는 차의 기능적인 측면(달릴 수 있는 능력 또는 수송 능력)이 빠져 있기 때문이죠! 그래서 이러한 기능을 나타낼 수 있는 방법이 필요한 것입니다. 이러한 차의 특성과 기능을 레코드 형으로 표현하기에는 부족하기 때문에, 이러한 특성과 기능을 나타낼 수 있는 새로운 데이터 형을 생각해 낸 것이죠! 그것이 바로 클래스라고 생각하시면 됩니다.
따라서 클래스는 레코드(record)형의 확장판이라고 말할 수 있습니다. 레코드형이 어떤 속성을 나타내는 데이터인 필드(field)로 이루어진 것에 비해, 클래스는 여기에다가 행동 양식 또는 기능을 나타낼 수 있는 메서드를 추가로 가지고 있습니다. 위의 자동차의 경우에 수송과 운전이라는 기능을 클래스를 이용해 구현한다면 다음과 같은 것입니다.
TCar=class
CarName:string;
Weight:longint;
Exhaust:longint;
Manufacture:string;
function GetDrive;
function GetTransport;
end;
레코드 형이 record라는 키워드를 사용해 선언하듯이 ,클래스는 class라는 키워드를 이용해 선언합니다. 그리고 새로운 클래스를 선언할 때 그 클래스의 조상이 될 클래스를 적어주어야 하며, 조상 클래스를 명시하지 않으면 ,새로운 클래스의 조상은 TObject가 됩니다. 즉 TCar=class
는 다음과 같은 의미입니다. TCar=class(TObject) 그리고 클래스형은 다른 형과는 달리 변수 선언 부분이나 함수 또는 프로시저 내부에 선언할 수 없고, 유니트의 가장 위의 형 선언부에 선언해야만 한다는 특징을 가지고 있습니다.
클래스와 오브젝트 그리고 인스턴스클래스와 오브젝트는 종종 같은 의미로 사용하기 때문에 , 정확히 두 용어 사이의 차이점이 무엇인지 우리를 혼란스럽게 만듭니다. 그러나 이 두 용어 사이의 차이점은 분명합니다.
클래스를 선언하는 것만으로 , 이 클래스가 메모리에 할당되고 바로 사용할 수 있는 것은 아닙니다. 그러기 위해서는 클래스 형의 변수를 만들어야 합니다. 이 둘 사이의 관계는 변수와 변수형 사이의 관계와 동일합니다. 즉 I:Integer 라는 관계처럼 클래스와 오브젝트는 다음과 같이 표현됩니다.
Button1:TButton;
위에서 Button1이 오브젝트이고 TButton이 클래스입니다. 이 선언으로 인해 TButton 클래스 형의 변수인 Button1이 메모리에 만들어지며, Button1를 통해 TButton 클래스가 구현되는 것입니다. 즉 오브젝트는 클래스형의 변수입니다. 어느 정도 감이 잡히시죠!
클래스형의 변수를 선언함으로 인해 메모리상에 그 클래스의 실체가 생기게 되는데 이때 메모리상에 구현된 클래스의 실체를 인스턴스(instance)라고 합니다.
그리고 오브젝트는 클래스 형의 변수이기 때문에 일반 변수처럼 여러개의 오브젝트를 한꺼번에 선언할 수도 있습니다.
Button1, Button2, Button3 : TButton;
생성자(constructor)와 소멸자(destructor)
생성자(constructor)는 새로운 오브젝트를 생성하고 초기화하는 역할을 담당합니다. 즉 새로운 오브젝트를 메모리에 할당하고, 그 영역을 초기화합니다. 오브젝트가 메모리에 할당되면 , 그 안에 들어있는 데이터는 특정한 값이 아닌 임의의 값으로 채워져 있습니다. 따라서 오브젝트에 할당된 데이터를 초기화하지 않으면 , 나중에 이 데이터를 참조할 때 예상치 못한 결과를 초래할 수도 있습니다. 그래서 이 데이터를 초기화하게 되는데, 순서형의 타입은 0으로 설정하고,포인터의 값은 nil로 , 그리고 문자열을 빈 문자열로 초기화합니다. 생성자는 procedure나 function과 달리 constructor 예약어로 시작합니다.
소멸자(destructor) 또는 파괴자는 생성자의 반대 역할을 수행합니다. 즉 오브젝트를 파괴하는데 사용합니다. 소멸자는 오브젝트에 할당한 메모리를 해제합니다. 오브젝트의 사용이 끝나면 오브젝트가 사용했던 메모리를 다시 돌려주어야 합니다. 만약 사용이 끝나고서도 메모리를 돌려주지 않으면 다른 응용 프로그램에서 이 오브젝트에 할당된 메모리를 사용할 수 없습니다. 따라서 시스템의 자원이 줄어들게 됩니다. 이 과정을 자동으로 수행하는 것이 바로 소멸자입니다. 소멸자는 destructor 예약어로 시작합니다.
생성자와 소멸자는 델파이에 의해 오브젝트가 생성될 때 , 자동적으로 호출되므로 일부러 생성자와 소멸자를 사용할 필요가 없습니다. 즉 어떤 클래스에서 생성자와 소멸자를 정의하지 않으면 , 델파이는 그 클래스 조상의 생성자와 소멸자를 이용해 메모리를 할당하고, 데이터를 초기화하며, 오브젝트의 사용이 끝난 후에는 메모리를 해제합니다. 만약 그 조상도 역시 생성자나 소멸자를 가지고 있지 않으며, 다시 그 위의 조상의 생성자와 소멸자를 이용합니다. 이런 식으로 쭉 거슬러 올라가서 최종적으로는 TObject에서 생성과 소멸을 처리합니다.
그러면 생성자나 소멸자를 어떤 때에 이용하느냐 하면 생성이나 소멸 과정 등에 특별히 해야만 할 다른 일이 있을 때 사용합니다.
다음의 예를 한 번 살펴보기로 하겠습니다.
type
TDate=class
private
Year:integer;
Month:integer
Day:integer;
public
constructor Create;
function GetDate:TDateTime;
end;
constructor TDate.Create;
begin
Year:=1990;
Month:=1;
Day:=1
end;
begin
Date1:TDate;
위의 클래스의 경우 Date1이 생성되면 Year, Month, Day 모두 0으로 초기화됩니다. 그러나 년, 월, 일이 모두 0의 값을 가지는 것은 분명 불합리한 일입니다. 따라서 초기화한 후에 이들 값을 다시 의미있는 새로운 값으로 설정해야 합니다. 이런 경우에 생성자를 이용하면 됩니다. 위에서 생성자를 통해 Year, Month, Day가 각각 1990, 1, 1로 설정됩니다. 이제는 소멸자를 사용하는 예에 대해서 알아보기로 하겠습니다.
TPictureView=class
private
FPicture:TBitmap;
public
Filename:string;
constructor Create;
destructor Destroy;
end;
constructor TPicture.Create;
begin
FPicture:=TBitmap.Create;
end;
destructor TPicture.Destroy;
begin
FPicture.Free;
end;
위의 예에서 소멸자를 사용하지 않는다고 가정해 봅시다. 그러면 TPictureView 형의 오브젝트 변수가 파괴될 때, 이 오브젝트가 차지하고 있
던 메모리만 해제하고 FPicture에 할당된 메모리는 해제하지 않습니다. 따라서 오브젝트가 파괴될 때 FPicture에 할당된 메모리도 함께 해제해야 합니다. 그러기 위해서 소멸자를 이용해 오브젝트가 파괴될 때 FPicture에 할당된 메모리도 함께 해제합니다.
클래스 참조(Class-reference )
클래스 참조는 오브젝트나 오브젝트 참조와는 달리 클래스형에 대한 참조를 나타냅니다. 이 클래스 참조형은 아래와 같이 class of라는 키워드로 시작합니다.
class of 클래스형
오브젝트가 해당 오브젝트의 형에만 사용할 수 있는 것에 비하여, 클래스참조는 해당 클래스와 그 클래스의 모든 자손 클래스에 대해 참조할 수 있다는 것이 다른 점입니다. 사용방법은 아래와 같습니다.
TComponentClass=class of TComponent
TControlClass=class of TControl
이 클래스 참조형의 인스턴스는 그 클래스로부터 파생한 어떠한 클래스형도 참조할 수 있습니다. 즉 위에서 TComponentClass는 TComponent로 파생한 어떠한 클래스(모든 컴퍼넌트)도 참조할 수 있고,TControlClass는 TControl에서 파생한 클래스를 참조할 수 있습니다.
이 클래스 참조를 어떤 경우에 사용하느냐 하면, 동적으로 컴퍼넌트를 생성할 때 자주 사용합니다. 컴퍼넌트 팔레트에서 특정 컴퍼넌트를 폼에 떨어뜨릴 때, 폼에 컴퍼넌트를 보여주는 이면에는 위의 클래스 참조가 활약하고 있습니다.
클래스 참조에 대한 예를 살펴보기 위해서는 델파이의 데모 디렉토리에 있는 DynaInst라는 예를 살펴보시면 클래스 참조의 강력함을 느낄 수가 있을 겁니다.
클래스 메서드(Class Method)
클래스에서는 일반적인 메서드와 비슷합니다. 그러나 일반 메서드 오브젝트 참조를 통해 사용되는 것과는 달리, 클래스 참조를 통해 사용된다는 것이 다릅니다. 이 말이 무엇을 뜻하는지 알기 위해 다음의 경우를 살펴봅시다.
type
TClassSample=class
function NormalMethod:string;
class function ClassMethod:string;
end;
class procedure TClassSample.ClassMethod;
begin
Result:='Class Method Sample';
end;
procedure TClassSample.NormalMethod;
begin
Result:='Class Method Sample';
end;
var
S:string;
Cl:TClassSample;
begin
S:=TClassSample.ClassMethod; {클래스 참조를 통한 호출}
Cl:=TClassSample.Create;
S:=Cl.NormalMethod;
S:=Cl.ClassMethod; {오브젝트 참조를 통한 호출}
end.
클래스 메서드의 선언 및 정의에서 procedure나 function 키워드 앞에 class라는 예약어를 포함해야 합니다. 실행시간 형정보(RTTI-RunTime Type Information)RTTI는 실행 시간에 형(type)에 대한 정보를 제공합니다. 즉 실행 시간에 형에 대한 정보를 알고자 하는 경우에 RTTI를 이용하게 됩니다. RTTI에는 is와 as라는 연산자가 있어서 오브젝트의 형을 검사할 수도 있고,형에 대한 타입캐스트(typecast)도 행할 수 있습니다. 그러면 왜 실행시간에 형에 대한 정보를 알아야 할까요? 그것은 실행 시간에 오브젝트의 정확한 형을 모르는 경우가 있습니다. 이런 경우 그 오브젝트에 속성이나 메서드를 함부로 사용하게 되면 예외가 생기게 됩니다. 따라서 이러한 문제점을 해결하기 위해 실행시간 형정보를 이용합니다. 아래의 클래스 연산자에서 이벤트 위임시 이 문제에 대해 다루게 될 것입니다.
RTTI를 이용하면 위와 같은 장점도 있지만, 단점도 있습니다. 그것은 형에 대한 정보를 조사하기 위해 모든 클래스를 조사해야 하므로 프로그램의 속도가 느려진다는 점입니다. 그러나 필자의 생각으로는 RTTI의 장점이 이러한 단점을 보충하고도 남는다고 생각합니다.
클래스 연산자
오브젝트 파스칼에는 is와 as라는 두가지 클래스 연산자가 있습니다.
is 연산자
is 연산자는 오브젝트 참조형이 특정한 클래스에 속하는지 검사하는데 사용합니다. is 연산자를 사용하는 구문은 다음과 같습니다.
ObjectRef is ClassRef
여기서 ObjectRef는 오브젝트 참조이고, ClassRef는 클래스 참조입니다. 위의 문은 ObjectRef가 ClassRef에서 유도된 클래스의 인스턴스이거나
ClassRef의 인스턴스인 경우에 참이고, 그렇지 않으면 거짓이 됩니다. 만약 ObjectRef가 nil이면 위의 문은 거짓이 됩니다. 다음의 예를 한 번 살펴보십시오.
procedure TSample.Button1Click(Sender:TObject);
begin
if (Sender is TButton) then
TButton(Sender).Caption:='Test'
else if (Sender is TEdit) then
TEdit(Sender).Text:='Test'
else if (Sender is TPanel) then
TPanel(Sender).Caption:='Test';
end;
위의 예에서 Button1Click이라는 프로시저이기 때문에 클릭한 오브젝트는 무조건 TButton이라고 생각할 수 있지만, 위임을 통해 TEdit나 TPanel의 클릭 이벤트도 TButton의 Click 이벤트에 연결할 수 있기 때문에 틀릴 수도 있다는 것에 유의하시길 바랍니다.
as 연산자
is 연산자는 한 오브젝트가 어떤 클래스와 일치하는가를 알아보는데 사용하는데 비하여 as 연산자는 한 오브젝트를 특정한 클래스형으로 타입캐스트하는데 사용합니다. 사용 구문은 다음과 같습니다.
ObjectRef as ClassRef
여기서 ObjectRef는 오브젝트 참조이고, ClassRef는 클래스 참조입니다.
procedure TSample.Button1Click(Sender:TObject);
begin
(Sender as TButton).Caption:='Test'
end;
상속(inheritance)
상속은 말 그대로 물려받는다는 것을 의미합니다. 그러면 누구에게서 무엇을 물려받느냐 하면 조상 클래스에게서 조상 클래스의 모든 것(속성,메서드)을 물려받습니다. 조상 클래스에서 불필요한 속성이나 메서드가 있다고 해서 , 그것을 빼고 나머지만 물려 받을 수 없고 모두 물려받아야 합니다.
그러면 상속이란 것이 왜 필요한가 생각해 보기로 하겠습니다.
리스트 박스를 예로 생각해 보겠습니다. 리스트 박스에 새로운 속성이나 메서드를 추가하고자 할 때 어떻게 해야 할까요? 가장 간단한 방법은 리스트박스 클래스의 소스를 수정하는 것입니다. 그런데 여기에는 문제점이 있습니다. 먼저 어떤 클래스를 수정할 경우 그 클래스에 대한 소스가 필요합니다. 그러나 어떤 클래스에 대한 소스는 있는 경우가 있겠지만 대부분 소스가 없는 것이 일반적입니다. 이런 경우 그 클래스에 새로운 속성을 추가할 방법이 없습니다. 이때 상속을 이용하면 조상 클래스의 소스가 없어도 새로운 속성이나 메서드를 추가할 수 있습니다. 다음으로 마침 소스가 있어서 그 소스를 이용해 해당 클래스를 수정하는 경우에도 문제가 생깁니다. 특정 클래스를 수정한 경우, 그 클래스를 이용한 어플리케이션 또한 영향을 받게 됩니다. 이것은 바람직하지 못한 결과를 초래할 수도 있습니다. 따라서 원래 클래스는 그대로 놔두고 , 이 클래스에다가 어플리케이션에서 필요한 속성 또는 메서드를 가지는 새로운 클래스를 만들면 위와 같은 문제점을 방지할 수 있습니다.
상속은 새로운 클래스를 선언할 때 자동적으로 이용 가능합니다. 아래와 같은 선언의 경우
TNewListBox=calss(TListBox)
TNewListBox 클래스는 조상 클래스가 TListBox인데, TListBox의 모든 데이터와 메서드를 물려 받게 됩니다. 또한 이 TNewListBox 클래스를 이용해 다시 새로운 클래스도 만들 수 있습니다.
TNewListBox1=calss(TNewListBox)
그리고 한 클래스에서 여러개의 새로운 클래스를 유도할 수도 있습니다.
TNewListBox2=class(TNewListBox)
정보 은폐,캡슐화(encapsulation)
클래스는 많은 데이터 또는 메서드를 가질 수 있습니다. 그러나 클래스를 사용하기 위해서 이 모든 데이터와 메서드를 다 알 필요는 없습니다. 더욱이 이 중에서 대부분의 데이터와 일부 메서드는 클래스 내부에서만 사용되기 때문에 , 외부에서는 알 필요가 없는 경우가 많습니다. 따라서 클래스의 데이터와 메서드 중에서 필요한 것만 접근할 수 있도록 하는 것이 클래스를 유지 관리하는데 도움이 됩니다.
자동차를 예로 들어 보겠습니다. 브레이크를 밟거나 가속페달(악셀레이터)을 밟게 되면, 자동차의 속도가 느려지거나 빨라지게 됩니다. 그러나 어떤 과정을 통해서 속도가 빨라지거나 느려지는가를 알 필요는 없습니다. (물론 알고 있으면 더욱 좋겠지만) 우리가 단지 알아야 할 것은 브레이크를 밟으면 속도가 느려진다는 사실입니다. 브레이크를 밟게되면 자동차 내부에서 속도를 줄이게 되기 때문에, 우리는 속도를 줄일 수 있습니다.
위와같이 클래스에서 내부에서 사용하는 데이터나 메서드를 외부에서 접근하지 못하도록 숨기는 것을 정보은폐 또는 캡슐화라고 합니다. 클래스를 캡슐화하게 되면 , 나중에 클래스의 내부 구조가 바뀌더라도 외부에는 영향을 주지 않으므로 어플리케이션을 다시 수정하지 않고서도 계속 사용할 수 있습니다.
그래서 클래스에는 클래스에 대한 접근을 지정하는 네가지의 키워드가 있습니다. 그것은 각각 private, protected, public, published 입니다. 이제 네가지 접근 지정자에 대해 하나씩 알아보도록 하겠습니다.
private
private는 접근을 최소한으로 억제하는 키워드입니다. private에 선언된 데이터나 메서드는 해당 클래스가 포함된 유니트 내에서만 접근이 가능하고 , 다른 유니트에서는 접근할 수 없습니다. private에는 주로 대부분의 데이터(필드)와 클래스 내부에만 사용하는 메서드가 들어 갑니다. 데이터는 클래스 내부에서만 사용하는 것이 보통으로 데이터를 private가 아닌 접근 지정자에 선언하는 것은 찾아 보기가 힘들 것입니다. 그리고 클래스 내부에서만 사용하는 메서드도 외부에 그 존재를 알릴 필요가 없기 때문에 private에 선언합니다.
public
public는 접근을 최대한으로 보장하는 키워드입니다. 그래서 어떤 프로그램이나 유니트에서도 public에 선언된 데이터나 메서드에 접근할 수 있습니다. 접근에 제한이 없는 셈이죠!
public에는 외부와의 인터페이스에 필요한 메서드를 선언합니다. 어떤 클래스이든지 그 클래스의 기능이 있으므로 , 이 기능을 구현하는 메서드는 public에 선언해야 합니다.
protected
public는 private와 public의 중간 단계입니다. 즉 해당 클래스가 있는 유니트 밖에서는 접근을 할 수 없지만, 이 클래스의 자손은 접근이 가능합니다. protected는 경우에 따라 그 존재를 외부에 알릴 필요가 있을 수도 있고 아닐 수도 있는 , 그 접근 범위가 명확하지 않는 데이터나 메서드를 선언하는데 사용합니다. 즉 , A라는 클래스의 자손이 B,C 두가지가 있는데 자손 B에서는 A의 PROCEDURE1이라는 프로시저가 필요하고 C에서는 필요없다면 이 procedure1 프로시저는 private에도 선언할 수 없고, public에도 선언할 수 없기 때문에 protected에 선언해야 합니다.
VCL의 계보를 살펴보면 TCustom으로 시작하는 많은 클래스가 있는데, 이 클래스에는 중요한 메서드나 속성이 모두 protected에 선언되어 있는 것을 볼 수 있을 겁니다. 이것은 자손 클래스에서 필요한 메서드나 이벤트,속성을 public으로 선언할 수 있도록 배려한 것입니다. 전부 ublic으로 선언하면 되지 않겠느냐 생각할 수 있지만, 조상 클래스에서 public으로 선언한 것을 자손 클래스에서 필요없다고 private에 선언할 수 없습니다. 그래서 그 사용 여부가 불명확한 것은 전부 protected에 선언하게 되는 것입니다.
published
published는 한가지를 제외하고는 public과 동일합니다. 즉 published에 선언된 것은 실행 시간 형 정보(RTTI)가 추가된다는 점입니다. 그리고
published에 선언된 속성은 설계 시간에 object inspector에 나타납니다. 이것이 published를 사용하는 가장 큰 이유입니다. published에 선언되지 않는 속성은 설계 시간에는 이용할 수 없고, 오로지 실행 시간에만 사용할 수 있습니다.
메서드를 published에 선언하는 아무 의미가 없습니다. 이것은 public으로 선언하는 것과 같은 으미를 가질 뿐입니다
'Delphi' 카테고리의 다른 글
| [델파이] TDateTime (1부) (0) | 2008/08/05 |
|---|---|
| 클래스(Class) (0) | 2008/08/05 |
| 델파이 클래스 기본 (0) | 2008/08/05 |
| 델파이에서 어셈블러를 써보자 (0) | 2008/07/18 |
| 델파이 클래스 기본 (0) | 2008/07/18 |
| [델파이] 지정한 폴더에 들어 있는 파일을 표시 (2) | 2008/07/09 |



댓글을 달아 주세요