Replies: 4 comments 2 replies
-
|
저는 타입 계층을 목적으로 상속을 사용할 때 핵심은 즉, 단순히 현실 세계에서 is-a처럼 보이는지가 아니라, 예를 들어 Penguin is a Bird는 현실 세계의 분류로는 맞지만, class Bird {
void fly() {
// 날 수 있음
}
}
class Penguin extends Bird {
@Override
void fly() {
throw new UnsupportedOperationException();
}
}이 경우 클라이언트는 Bird 타입을 보고 “날 수 있는 객체”라고 기대합니다. void makeFly(Bird bird) {
bird.fly();
}그런데 여기에 Penguin이 들어오면 런타임 예외가 발생합니다. 클라이언트는 Bird라면 당연히 날 수 있다고 가정하고 코드를 작성했는데, 런타임에야 예외가 터집니다. 그래서 타입 계층을 위한 상속은 단순한 코드 재사용이 아니라, 조건을 지키지 않았을 때의 문제는 크게 3가지라고 봅니다. 1. 클라이언트의 기대가 깨진다부모 타입을 사용하는 클라이언트는 부모 타입의 public 메서드가 정상적으로 동작할 것이라고 기대합니다. 그런데 자식 클래스가 특정 메서드를 지원하지 않거나, if (bird instanceof Penguin) {
// 따로 처리
}이렇게 되면 다형성을 활용하는 것이 아니라, 오히려 타입을 직접 검사하는 코드가 늘어납니다. 2. 부모 클래스의 변경이 자식 클래스에 예상치 못한 영향을 준다상속은 부모의 구현과 자식의 구현을 강하게 묶습니다. 즉, 상속이 단순히 “코드를 물려받는 편한 방법”처럼 보이지만, 3. 잘못된 추상화가 고착된다타입 계층은 한 번 만들어지면 클라이언트 코드 전반에 퍼지기 쉽습니다. 처음에 부모 타입을 잘못 잡으면, @Override
void fly() {
// 아무것도 하지 않음
}
또는
@Override
void fly() {
throw new UnsupportedOperationException();
}이런 코드는 “이 타입은 부모 타입이다”라는 선언과 실제 행동이 어긋나는 상태라고 생각합니다. 최종 정리그래서 저는 타입 계층을 위한 상속의 기준을 이렇게 정리할 수 있을 것 같습니다. 즉, is-a 관계는 출발점일 뿐이고, 더 중요한 것은 행동 호환성이라고 생각합니다. 현실 세계의 분류상 “A는 B다”가 성립하더라도, |
Beta Was this translation helpful? Give feedback.
-
Is-a 관계를 지키지 않은 경우Stack을 ArrayList()로 구현하는 예시를 들어보겠습니다. class Stack extends ArrayList {
}삽입, 삭제, 조회를 구현하는 데 문제가 없지만, 사용하는 사람 입장에서 어색함을 느낄 것입니다. Stack stack = new Stack();
stack.add(0, "A");
stack.remove(0);
stack.get(3);이를 개선하기 위해 직접 push, pop, peek을 직접 구현하면, 사용하지 않는 부모의 메서드까지 구현해하며, 알게되며, 외부로 노출됩니다. 부모 클래스의 타입으로 자식 클래스를 사용하는 게 적절하지 않은 경우유명한 이론 중, Square-Rectangle 문제가 있습니다. 정사각형은 직사각형이지만(is-a 만족), 제 생각상속은 단순히 재사용하기 보다는, 부모 타입의 행동 규악을 자식도 지키겠다는 약속이 보장되어야 적절히 사용할 수 있다고 생각합니다!
|
Beta Was this translation helpful? Give feedback.
-
|
타입 계층을 구현하기 위한 도구 중 하나가 상속이라고 생각하는데요(이를 서브 타이핑이라고 합니다.), 이 타입 계층이 필요한 이유를 먼저 이해해야, 왜 서브타이핑이 해당 규칙을 준수해야 하는지 설명할 수 있을 것 같습니다. 우선 오브젝트 13장에서 이에 대해 학습했던 기록이 있어 남깁니다! 학습기록 타입 계층 구현이 필요한 이유우선 객체 지향에서 타입은 동일한 행위를 갖는 집합을 의미합니다. 즉, 동일한 퍼블릭 인터페이스를 갖는 집합은 동일 타입으로 이해할 수 있는데요, 이 동일한 메시지(인터페이스)에 대해 서로 다르게 행동할 수 있는 구체적인 클래스를 구현하기 위해 타입 계층을 사용합니다. 타입 계층 구현의 이유는 클라이언트의 기대를 기준으로 객체의 역할을 분리하기 위해 필요합니다. Q1. IS-A를 모델링해야 하는 이유결국 근본적으로는 객체 지향의 가장 중요 분류 기준인 "행위"로부터 출발하기 때문에, IS-A관계 또한 행위의 측면에서 동일한지 고려해야합니다. 여기에 추가적으로 이 행위를 요청하는 클라이언트의 입장도 함께 고려해줘야 한다고 생각합니다. 단순히 현실세계에서의 IS-A가 아닌, "실제 이 메시지를 전송하는 클라이언트 입장에서" IS-A가 만족하는가가 더 중요하다고 생각합니다. Q2. 행동 호환성을 만족해야하는 이유앞서 말했다시피, 타입 계층의 구현 이유인 객체 역할 분리와 다형성을 구현하기 위함인데요, 그렇기 때문에 클라이언트가 기대하는 행위를 구체적인 구현체를 몰라도 안정적으로 응답받을 수 있어야 합니다. 그렇게 때문에 같은 타입 계층에 속하는 객체들은 같은 행위를 제공해야 합니다. 정리하자면, 해당 행위를 호출하는 클라이언트 입장에서는 타입 계층의 구조를 몰라도 되고, 계약 조건에 지장이 있으면 안되기 때문에, 행동 호환성이 중요하다고 생각합니다. |
Beta Was this translation helpful? Give feedback.
-
|
위에서 이미 잘 설명해주셨지만 오브젝트의 내용을 인용해 한 번 더 정리해보겠습니다! 상속을 타입 계층을 목적으로 사용할 때는 단순히 “A는 B의 한 종류다”라는 개념적인 관계만으로 판단하면 안됩니다.
책에서 Bird 예시를 들어 해당 케이스를 설명하고있습니다. 장기 미션에서 비슷한 사례를 경험했는데요, 포 말의 경우 이동 경로 사이에 다른 말이 있어야만 움직일 수 있습니다. 당시에는 Piece가 확장 가능한 구조처럼 보였지만, 실제로는 부모 타입만으로 안전하게 다룰 수 없는 상태가 된 것입니다. 다시 구현해본다면 Piece에 기본 규칙을 두기보다, 이동 가능 여부를 판단하는 책임을 각 기물에게 위임하거나, Piece 하나를 모두 상속받게 만드는 방식 자체를 검토해볼 것 같습니다. |
Beta Was this translation helpful? Give feedback.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
-
상속을 사용하는 이유가 2가지가 있는데요,
타입 계층을 목적으로 상속을 사용할 때는,
행동 호환 가능성)2개의 조건을 만족시켜야한 상속을 사용하라고 말한다.
왜 그래야 할까요?
그렇지 않았을 때에는 어떤 문제점들이 있을 수 있을까요?
Beta Was this translation helpful? Give feedback.
All reactions