티스토리 뷰

Android/Kotlin

클래스 상속

취뽀가자!! 2019. 4. 27. 22:12

코틀린은 기본적으로 상속이 불가능합니다. 상속을 하기 위해서는 open이라는 키워드를 사용해야 합니다. 이렇게 하는 이유는 객체 지향에서 상속을 잘 못 사용할 경우 많은 문제가 발생하기 때문입니다. 이를 이해하기 위해 상속에 대해 더 구체적으로 알아봅시다.

 

상속은 반복되는 중복 코드를 줄여 주는 객체 지향 프로그래밍 핵심 기법 중 하나입니다. 상속은 2가지 측면에서 접근 가능합니다. 하나는 코드 구현에 대한 상속이며, 또 다른 하나는 인터페이스 집합에 대한 상속입니다.

 

인터페이스 상속은 코드를 포함하고 있지 않기 때문에 다중 상속이 가능합니다. 하지만 구현 상속은 다중 상속 시 문제가 생깁니다. 구현 상속은 실제 코드가 포함되어 있기 때문에 만약 상속받는 2개의 클래스에 똑같은 이름의 메서드가 있다면 어느 메서드를 호출해야 할지 애매해지기 때문입니다.

 

이 외에도 구현 상속은 클래스 설계가 복잡해질 때 다양한 문제들이 생겨납니다. 대표적인 것이 fragile base class(취약한 기반 클래스) 라는 문제입니다. 이것은 하위 클래스에서 상위 클래스의 메서드를 오버라이딩하면서 발생하게 되는데, 그 이유는 클래스를 사용하는 코드에서는 클래스의 실제 구현에는 관심을 가지지 않아야 하기 때문입니다. Java를 사용하여 이해해 봅시다.

// 이동 가능한 객체
public class MoveObject {
    protected int speed;

    public void addSpeed(int param) {
        this.speed = speed + param;
    }

    public int getSpeed() {
        return speed;
    }
    
    public int x, y;
}

이동 속도는 항상 0인 새 오브젝트가 필요해져서 재사용 하기 위해 상속을 받아 만들었다고 가정해 보죠.

public class CantMoveObject extends MoveObject {

    // 생성자에서 speed 를 0 으로 만듬.
    public CantMoveObject(){
        this.speed = 0;
    }

    // addSpeed 메소드를 오버라이드 함
    @Override
    public void addSpeed(int param) {
        // 움직일수 없는 오브젝트이므로 아무것도 하지 않음
    }
}

이런 상속은 컽보기에는 문제가 없을 것 같습니다. 하지만 이런 식의 상속은 객체 지향의 주요 개념들을 위반하게 되죠. 또 다른 예를 들어 다음과 같은 클래스가 있다고 생각해 봅시다.

public class Calculator {
    // 명중률 계산 함수, 파라미터로 MoveObject 객체와, 공격자의 명중률을 받는다.
    public static int calcAccuracy(MoveObject moveObject, int attackerAccuracy){

        // moveObject 의 speed 가 0 인 경우 잘못된 상황으로 판단하여 스피드 1 을 추가
        if(moveObject.getSpeed() == 0){
            moveObject.addSpeed(1);
        }

        // 위의 코드로 moveObject.getSpeed() 가 0 이 나오지 않는다고 생각하고 나눗셈을 함.
        double resultAccuracy = attackerAccuracy / moveObject.getSpeed();
        return (int) resultAccuracy;
    }
}

Calculator 클래스는 calcAccuracy() 함수에서 moveObject의 스피드가 0인 경우 1을 추가해 줍니다. 이 경우, MoveObject 클래스만 있는 경우에는 문제가 되지 않지만 MoveObject가 사실 CantMoveObject이라면 0으로 값을 나누게 되어 에러가 발생합니다.

 

이 상황을 테스트 코드에서 확인해 보죠.

public class JavaTest {
    @Test
    public void testMoveObject() {
        MoveObject moveObject = new MoveObject();
        // MoveObject 의 speed 를 아직 설정하지 않았으므로 0 인 상태로 calcAccuracy 함수를 호출
        int accuracy = Calculator.calcAccuracy(moveObject, 3);

        // Calculator.calcAccuracy() 는 MoveObject 의 스피드가 0 인 경우 자동으로 스피드를 1로 만들고 계산함
        Assert.assertEquals(1, moveObject.getSpeed());

        // MoveObject 의 실제 구현체를 CantMoveObject 로 생성
        MoveObject cantMoveObject = new CantMoveObject();
        // 똑같이 speed 를 설정하지 않고 calcAccuracy 를 호출
        accuracy = Calculator.calcAccuracy(cantMoveObject, 3);
        // 위 코드에서 이미 에러가 발생해 실행되지 않음
        Assert.assertEquals(1, cantMoveObject.getSpeed());
    }
}

아마 실행하면 'ArithmeticException / by zero' 에러가 발생할 겁니다. 이 에러가 발생하는 이유는 0으로 나눗셈을 했기 때문입니다.

 

위 테스트 코드에서 보면, 한 번은 MoveObject의 실제 인스턴스를 MoveObject로 하고 다음에는 CantMoveObject로 하고 있습니다. MoveObject를 사용하는 Calcurtor 입장에서는 MoveObject가 실제로는 어느 클래스인지 상관없이 사용할 수 있어야 합니다. 그런데 상속으로 오버라이딩을 하면서 Calcurator는 MoveObject가 실제로 어떤 클래스인지 알 수 없어 에러가 발생하게 된 것입니다.

 

위 상황은 객체 지향의 주요 원칙을 위반하게 됩니다. 캡슐화의 주요 목적 중 하나는 클래스를 사용하는 측면ㅇ에서 해당 클래스의 구체적인 사항을 모르게 하는 것입니다. 그런데 조금 전 예제는 구체적인 구현 클래스를 알아야만 하므로 캡슐화가 깨졌다 고 볼 수 있는 것이죠.

 

이런 예는 앞서 말한 fragil base class의 일반적인 사례중 하나입니다. 그렇기 때문에 Java에서 유명한 책 중 하나인 Effective Java에서는 상속에 대하여. '상속을 위한 설계와 문서를 갖추거나, 그렇지 않은 경우 구현 상속을 금지하라'라고 합니다.

 

이와 같은 이유로 코틀린에서는 상속을 불가능하게 만들고, 상속시키고 싶을 때만 open키워드를 사용해 상속을 가능하게 만듭니다.

'Android > Kotlin' 카테고리의 다른 글

클래스 위임  (1) 2019.04.28
Getter 와 Setter  (0) 2019.04.27
댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/05   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
글 보관함