[오브젝트] 13장 - 서브클래싱과 서브타이핑
    2022-11-29 10:00
    object

    상속이 사용되는 두 가지 용도

    • 타입 계층을 구현하는 것

      • 부모 클래스
        • 일반적인 개념을 구현
        • 부모 클래스는 자식 클래스의 일반화
      • 자식 클래스
        • 특수한 개념을 구현
        • 자식 클래스는 부모 클래스의 특수화
    • 코드 재사용

      • 부모 클래스의 코드를 재사용하는 것이 가능
      • 단, 부모, 자식 클래스가 강하게 결합되어 변경하기 어려운 코드가 탄생
    • 상속의 사용 이유는 타입 계층을 구현하는 것이어야 한다.

      • 객체의 행동을 기반으로 타입 계층을 구성해야한다.

    1. 올바른 타입 계층을 구성하는 원칙

    개념 관점의 타입

    • 타입

      • 공통의 특징을 공유하는 대상들의 분류
      • 사물을 분류하기 위한 틀
      • ex) 자바, 루비, 자바스크립트 - 프로그래밍 언어 타입
    • 타입의 인스턴스 (객체)

      • 타입으로 분류되는 대상
      • ex) 자바 - 프로그래밍 언어의 인스턴스
    • 타입의 구성 요소

      • 심볼: 타입의 이름
      • 내연: 타임의 정의. 타입에 속한 객체들이 가지는 공통적인 속성, 행동
      • 외연: 타입에 속하는 객체들의 집합

    프로그래밍 언어 관점의 타입

    하드웨어는 데이터를 0과 1로 구성된 일련의 비트 조합으로 취급. 비트 자체에는 타입이라는 개념이 존재하지 않기 때문에 비트에 담긴 데이터를 문자열로 다룰지, 정수로 다룰지는 데이터를 사용하는 어플리케이션에 의해 결정된다.

    • 타입
      • 동일한 오퍼레이션을 적용할 수 있는 인스턴스들의 집합
      • 비트 묶음에 의미를 부여하기 위해 정의된 제약과 규칙
    • 타입의 두 가지 목적
      • 객체의 타입에 따라 적용 가능한 연산자의 종류를 제한하여 프로그래머의 실수를 방지
      • 타입에 수행되는 오퍼레이션에 대해 미리 약속된 문맥을 제공
        • a와 b에 부여된 타입이 + 연산자의 문맥을 정의.
        • new 연산자는 타입에 정의된 만큼 저장 공간을 할당하고 객체를 초기화하기 위해 타입 생성자를 자동으로 호출

    객체지향 패러다임 관점의 타입

    • 타입
      • 객체의 퍼블릭 인터페이스를 정의하는 것
        • 퍼블릭 인터페이스
          • 객체가 수신할 수 있는 메시지의 집합
      • 동일한 퍼블릭 인터페이스를 가지는 객체들은 동일한 타입
        • 객체가 수신할 수 있는 메시지를 기준으로 타입을 분류

    2. 타입 계층

    타입 계층은 포함 관계로 연결되어있다.

    퍼블릭 인터페이스 관점에서의 슈퍼타입과 서브타입

    • 슈퍼타입
      • 서브타입이 정의한 퍼블릭 인터페이스를 인반화시켜 상대적으로 범용적이고 넓은 의미로 정의한 것.
    • 서브타입
      • 슈퍼타입이 정의한 퍼블릭 인터페이스를 특수화시켜 상대적으로 구체적이고 좁은 의미로 정의한 것.
      • 서브타입의 인스턴스는 슈퍼타입의 인스턴스로 간주될 수 있다.

    3. 서브클래싱과 서브타이핑

    타입 계층을 구현할 때 지켜야 하는 제약사항

    언제 상속을 사용해야 하는가?

    상속의 올바른 용도는 타입 계층을 구현하는 것. 아래 두 질문에 모두 '예'라고 답할 수 있을때만 상속을 사용.

    • 상속 관계가 is-a 관계를 모델링하는가?
      • [자식 클래스]는 [부모 클래스]다 라고 말해도 이상하지 않는 경우
      • 단, 어휘적 정의가 아니라 기대되는 행동에 따라 타입 계층을 구성해야 한다.
    • 클라이언트 입장에서 부모 클래스의 타입으로 자식 클래스를 사용해도 무방한가? ⭐️
      • 상속 계층을 사용하는 클라이언트의 입장에서 부모 클래스와 자식 클래스의 차이점을 몰라야 한다. - 자식 클래스와 부모 클래스의 행동 호환성

    팽귄과 새는 is-a 관계로 묶을 수 있지만 새와 팽귄의 서로 다른 행동 방식 (날 수 있는지 여부)은 동일한 타입 계층으로 묶어서는 안된다고 경고한다.

    행동 호환성

    • 두 타입 사이에 행동이 호환될 경우에만 타입 계층으로 묶어야 한다.
    • 행동의 호환 여부를 판단하는 기준은 클라이언트의 관점
      • 클라이언트가 두 타입이 동일하게 행동할 것이라고 기대하면 두 타입을 타입 계층으로 묶을 수 있다.

    클라이언트의 기대에 따라 계층을 분리하라.

    행동 호환성을 만족시키지 않는 상속 계층을 유지하기란 쉽지 않다. 클라이언트의 기대에 맞게 상속 계층을 분리하라.

    • 인터페이스는 클라이언트가 기대하는 바에 따라 분리돼야 한다.
      • 인터페이스 분리 원칙 (ISP)
    • 두 클래스 사이에 행동이 호환되지 않는다면 올바른 타입 계층이 아니므로 상속을 사용해서는 안된다.

    서브클래싱과 서브타이핑

    • 서브클래싱
      • 코드 재사용 목적으로 상속을 사용하는 경우
      • 자식 클래스와 부모 클래스의 행동이 호환되지 않는다.
        • 자식 클래스의 인스턴스가 부모 클래스의 인스턴스를 대체할 수 없다.
    • 서브타이핑
      • 타입 계층 구성을 위해 상속을 사용하는 경우
      • 자식 클래스와 부모 클래스의 행동이 호환된다.
        • 자식 클래스의 인스턴스가 부모 클래스의 인스턴스를 대체할 수 있다. (LSP)
        • 부모 클래스는 자식 클래스의 슈퍼타입이 된다.
        • 자식 클래스는 부모 클래스의 서브타입이 된다.

    4. 리스코프 치환 원칙

    상속 관계로 연결한 두 클래스가 서브타이핑 관계를 만족시키며 서브타입은 그것의 기반 타입에 대해 대체 가능해야 한다.

    리스코프 치환 원칙을 위반하는 is-a 관계 (정사각형, 직사각형)

    public class Rectangle { private int x, y, width, height; public Rectangle(int x, int y, int width, int height) { // 생략 } public void setWidth(int width) { this.width = width; } public void setHeigth(int height) { this.height = height; } }
    public class Square extends Rectangle { public Square(int x, int y, int size) { super(x, y, size, size); } @Override public void setWidth(int width) { super.setWidth(width); super.setHeight(width); } @Override public void setHeight(int height) { super.setWidth(height); super.setHeight(height); } }
    public void resize(Rectangle rectangle, int width, int height) { rectangle.setWidth(width); rectangle.setHeight(height); assert rectangle.getWidth() == width && rectangle.getHeight() == height; }

    클라이언트가 resize() 메서드를 사용한다고 할때,

    • Rectangle을 사용하는 클라이언트는

      • Rectangle의 너비와 높이가 다를 수 있다는 가정하에 코드를 개발.
      • 하지만 Square 는 너비와 높이가 항상 같다.
      • Ractangle을 Square로 대체할 경우 Rectangle에 세워진 가정을 위반할 확률이 높다.
    • is-a 관계의 문장 앞에 "클라이언트 입장에서"라는 말이 빠져 있다고 생각하라.

    • 클라이언트 입장에서 퍼블릭 인터페이스의 행동 방식이 변경되지 않는다면 클라이언트의 코드를 변경하지 않고도 새로운 자식 클래스와 협력할 수 있게 된다.

    느낀점

    면접 단골 예상 질문이라며 인터페이스와 추상 클래스의 차이점에 대한 질문을 본적이 있습니다. 자바8, 9로 넘어오면서 부터는 인터페이스에서도 디폴트 메서드를 정의하거나 private 메서드를 정의하는것이 가능해 졌습니다.

    이처럼 인터페이스가 점점 추상 클래스의 역할까지 담당해 나가고 있고 인프런 강의 영상에서도 점점 추상클래스를 사용하지 않고 있다는 말을 들은적이 있습니다. 때문에 문득 그럼에도 추상클래스가 필요한 이유가 있을까?라는 의문을 갖고 찾아보게 되었습니다.

    • 인터페이스에선 final 메서드를 정의할 수 없다.
      • 서브클래스에서 어떠한 메서드가 재정의 (오버라이드)되는 것을 의도적으로 막기 위해서는 final 메서드가 필요하다
    • 인터페이스는 상태를 갖지 못한다.
    • 커뮤니케이션 가치
      • 인터페이스는 구현해야하는 존재
      • 구현될 필요가 없는 인터페이스를 의도하고 작성하였지만 누군가는 인터페이스를 보고 구현해야 할 존재로 바라볼 수 있다. 즉, 사람들의 예상과 다른 코드를 작성하여 오해를 불러 일으키는 코드를 작성하게 된다.
    • 생성자를 선언하지 못한다.

    https://www.quora.com/Do-we-need-abstract-classes-anymore-when-we-have-Java-8s-interfaces-with-default-and-static-methods