상속 예제
지난 포스팅(객체지향 프로그래밍 예제)에서 객체지향 예제를 알아봤는데, 도형을 그릴때 중복되는 변수와 메소드가 너무 많았다. 이번에는 상속을 통해서 코드를 줄여 보자.
우리가 머리 색깔이나 코 등을 부모님으로 부터 상속을 받는 것처럼, oop 에서 상속은 그 클래스가 다른 클래스로부터 특성을 물려받는 다는 것을 의미한다. 그 다른 클래스를 부모 클래스나 수퍼클래스라고 부른다. 원과 사각형은 Shpae으로 부터 상속을 받았기 때문에 Shpae의 두 인스턴스 변수를 갖는다.
참고: 상속 받는 인스턴스 변수들의 값을 직접 변경하는 것은 좋은 코딩 습관이 아니다. 인스턴스 변수를 변경하기 위해서는 변경하는 메소드를 만들어 사용하도록 한다.
상속 예제 코드
#import <Foundation/Foundation.h>
//모양 - 삭제 됨
//typedef enum {
// kCircle,
// kRectangle,
// kOblateSpheroid
// //kTriangle
//}ShapeType;
//삭제됨
////도형 구조체
//typedef struct{
// //ShapeType type;
// ShapleColor fillColor;
// ShapeRect bounds;
//
//}Shape;
//색상
typedef enum{
kRedColor,
kGreenColor,
kBlueColor
}ShapleColor;
//위치 및 크기 구조체
typedef struct{
int x, y, width, height;
}ShapeRect;
//ShapleColor 이넘 => NSString 타입 변경
NSString *colorName (ShapleColor colorName){
switch (colorName) {
case kRedColor:
return @"red";
break;
case kGreenColor:
return @"green";
break;
case kBlueColor:
return @"blue";
break;
}
return @"no color";
}
// ------------------------------------------------
// 모양 관련 부모 클래스 선언
@interface Shape : NSObject
{
ShapleColor fillColor;
ShapeRect bounds;
}
-(void) setFillColor:(ShapleColor) fillColor; //색상
-(void) setBounds:(ShapeRect) bounds; //위치, 크기
-(void) draw; //그리기 : 자식 클래스에서 재정의
@end
//부모 클래스 구현
@implementation Shape
-(void) setFillColor:(ShapleColor) c{
fillColor = c;
}
-(void) setBounds:(ShapeRect) b{
bounds = b;
}
-(void) draw{} //자식 클래스에서 재정의
@end //모양 관련 부모 클래스
// ------------------------------------------------
// 원 관련 클래스 : 부모 클래스 상속
@interface Circle : Shape
@end
@implementation Circle
//색깔 필터 재정의
- (void)setFillColor:(ShapleColor) c{
if (c == kRedColor) {
c = kGreenColor;
}
[super setFillColor:c];
}
//재정의
-(void) draw{
NSLog(@"원을 %@색으로 (%d, %d, %d , %d)에 그리시오" ,
colorName(fillColor) ,
bounds.x , bounds.y, bounds.width , bounds.height);
}
@end //원 클래스
// ------------------------------------------------
// 사각형 관련 클래스 : 부모 클래스 상속
@interface Rectangle : Shape
@end
@implementation Rectangle
//재정의
-(void) draw{
NSLog(@"사각형을 %@색으로 (%d, %d, %d , %d)에 그리시오" ,
colorName(fillColor) ,
bounds.x , bounds.y, bounds.width , bounds.height);
}
@end //사각형 클래스
// ------------------------------------------------
// 오목원 관련 클래스 : 부모 클래스 상속
@interface OblateSpheroid : Shape
@end
@implementation OblateSpheroid
//재정의
-(void) draw{
NSLog(@"오목원을 %@색으로 (%d, %d, %d , %d)에 그리시오" ,
colorName(fillColor) ,
bounds.x , bounds.y, bounds.width , bounds.height);
}
@end //오목원 클래스
// --------------------------------------------------
// 삼각형 클래스 : 부모 클래스 상속
@interface Triangle : Shape
@end
@implementation Triangle
//재정의
- (void) draw
{
NSLog(@"삼각형을 %@색으로 (%d, %d, %d , %d)에 그리시오" ,
colorName(fillColor) ,
bounds.x , bounds.y, bounds.width , bounds.height);
} // draw
@end // 삼각형
//도형 그리기
void drawShapes(__strong id shapes[] , int count){
for (int i = 0; i < count; i++) {
id shape = shapes[i];
[shape draw];
}
}
int main(int argc, const char * argv[]) {
id shapes[4];
//만약 원을 그릴때 빨간색말고 초록색으로 그려야 한다면?
//자식 클래스에서 setFillColor 재정의 필요
ShapeRect rect0 = {0,0,10,30};
shapes[0] = [Circle new];
[shapes[0] setBounds:rect0];
[shapes[0] setFillColor:kRedColor];
ShapeRect rect1 = {30,40,50,60};
shapes[1] = [Rectangle new];
[shapes[1] setBounds:rect1];
[shapes[1] setFillColor:kGreenColor];
ShapeRect rect2 = {15,19,37,29};
shapes[2] = [OblateSpheroid new];
[shapes[2] setBounds:rect2];
[shapes[2] setFillColor:kBlueColor];
//삼각형 추가
ShapeRect rect3 = { 47, 32, 80, 50 };
shapes[3] = [Triangle new];
[shapes[3] setBounds: rect3];
[shapes[3] setFillColor: kRedColor];
drawShapes(shapes, 4);
return 0;
}
원리 - 메소드 디스패치
객체는 메시지를 받았을 때 어떤 메소드를 실행해야 하는지 어떻게 알까? 예를들어 setFillColor: 의 코드는 Circle과 Rectangle 클래스에서 없어졌는데, Shape 코드는 Circle 객체에 setFillColor 메시지가 보내졌을 때 뭘 해야 하는지 어떻게 알까?
코드에서 메시지를 보내면 오브젝티브-C 의 메소드 디스패처가 현재 클래스에서 메소드를 찾는다. 디스패처가 메시지를 받은 객체의 클래스에서 메소드를 찾지 못하면 그 객체의 수퍼 클래스를 찾는다.
위 경우는 setFillColor 메소드를 처리하기 위해서 오브젝티브 C 메소드 디스패처는 메시지를 받은 객체를 조사하는데, 이 경우 Circle 클래스 객체가 된다. 이 객체는 자신의 클래스를 가리키는 포인터를 가지고 있고, 그 클래스는 자신의 코드를 가리키는 포인터를 가지고 있다. 디스패처는 정확한 코드를 실행하기 위해서 이런 포인터를 사용한다.
아래 그림에서는 Circle 클래스는 자신의 슈퍼 클래스인 Shape을 가리키는 포인터를 가지고 있다. 오브젝티브 C 의 메소드 디스패처는 메시지가 들어왔을 때 메소드의 정확한 구현 코드를 찾기 위해서 이 정보를 사용한다. Circle 객체에 setFillCorlor 메시지를 보내면 디스패처는 Circle에서 대응되는 메소드를 찾다가 없으면 수퍼클래스인 Shape에서 메소드를 찾는다. "여기서 찾을 수 없다면 이 클래스의 수퍼클래스를 찾아보자" 라는 동작은 필요하면 상속으로 연결된 모든 클래스들 사이에서 반복된다. 디스패처는 Circle, Shape에도 메소드를 발견할 수 없는 경우, 상속관계에서 Shaped의 수퍼클래스인 NSObject 클래스를 검사한다. 최상위 수퍼 클래스인 NSObject 에도 메소드가 없다면 런타임 에러를 발생 시킨다. 아울러 컴파일 시에 경고가 나타나게 될 것이다.
인스턴스 변수
Circle의 draw 메소드는 Shape에 선언된 인ㅇ스턴스 변수인 bounds와 fillColor를 어떻게 찾는 걸까?
새 클래스를 만들 때 , 그 객체는 수퍼클래스로부터 인스턴스 변수를 상속받아서 자신의 인스턴스 변수를 추가한다.
인스턴스 변수의 상속관계를 이해하기 위해서 새 인스턴스 변수가 추가된 shape 을 만들어 보자. 이 클래스의 이름은 RoundedRectangle이고 사각형의 코드를 둥글게 그리기 위한 반경 값(radius)를 담을 변수가 필요하다.
@interface RoundedRectangle : Shape
{
@private int radius;
}
@end
@implementation RoundedRectangle
@end
아래 그림은 둥근 사각형 객체의 메모리 배치를 보여준다. NSObject는 객체의 클래스를 가리키는 포인터를 담는 isa라고 하는 한 개의 인스턴스 변수를 선언하고 있다. 다음으로 Shape가 선언하고 있는 두 인스턴스 변수인 fillColor와 bounds가 있다. 마지막으로 RoundedRectangle이 선언하고 있는 인스턴스 변수인 radius 가 있다.
모든 메소드가 메시지를 받는 객체를 가리키는 포인터인 self 가 숨겨진 채로 호출 된다는 것을 기억하자. 메소드는 사용할 인스턴스 변수를 찾기 위해 self 포인터를 이용한다.
아래 그림은 둥근 사각형 객체를 가리키는 self 포인터를 보여준다. self는 상속관계에서 첫 번째 클래스의 첫 번째 인스턴스 변수를 가리킨다. RoundedRectangle 에서 상속관계는 NSObject에서 시작해서 Shape으로 이어지며 RoundedRectangle에서 끝나기 때문에, self는 첫번째 인스턴스 변수인 isa를 가리킨다. 오브젝티브 c 컴파일러는 각 클래스의 @interface 선언을 봤기 때문에, 한 객체의 인스턴스 변수의 배치도를 알고 있다. 이 중요한 정보를 가지고 컴파일러는 어떤 인스턴스 변수라도 찾기 위한 코드를 만들 수 있다.
메소드 오버라이딩
super에 메시지를 보내면 오브젝티브 c에 그 클래스의 수퍼 클래스에 메시지를 보내라고 요청하는 것이 된다. 만일 메소드가 그 클래스의 수퍼클래스에 정의되어 있지 않다면, 전에 살펴본 대로 상속관계에 있는 모든 클래스를 차례로 살펴 본다.
ios와 맥 os x 개발을 위한 오브젝티브 -c 2판 도서 참고
'아이폰 개발 > Objective C' 카테고리의 다른 글
메모리 관리 (0) | 2021.02.22 |
---|---|
컴포지션 (0) | 2021.02.22 |
객체지향 프로그래밍 예제 (0) | 2021.02.01 |
objective c 프로토콜 3 - 비공식 프로토콜 (0) | 2020.12.31 |
objective c 프로토콜 2 - 프로토콜의 채용, 상속 카테고리화 (0) | 2020.12.31 |