본문 바로가기
아이폰 개발/Objective C

상속 예제

by 인생여희 2021. 2. 1.

상속 예제

 

지난 포스팅(객체지향 프로그래밍 예제)에서 객체지향 예제를 알아봤는데, 도형을 그릴때 중복되는 변수와 메소드가 너무 많았다. 이번에는 상속을 통해서 코드를 줄여 보자.

 

 

 

 

우리가 머리 색깔이나 코 등을 부모님으로 부터 상속을 받는 것처럼, 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;
}

 

 

 

Object4_상속.zip
0.04MB

 

 

원리 - 메소드 디스패치

 

 

객체는 메시지를 받았을 때 어떤 메소드를 실행해야 하는지 어떻게 알까? 예를들어 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판 도서 참고