상속은 코드를 재사용하는 강력한 수단이지만 항상 최선은아니다. 잘못 사용하면 오류를 내기 쉬운 소프트웨어를 만들게 된다.
상위 클래스와 하위 클래스를 모두 같은 프로그래머가 통제하는 패키지 안에서라면 상속도 안전한 방법이다. 확장할 목적으로 설계되었고 문서화도 잘 된 클래스도 마찬가지로 안전하다. 하지만 일반적인 구체 클래스를 패키지 경계를 넘어 다른 패키지의 구체 클래스를 상속하는 일은 위험하다.
메서드 호출과 달리 상속은 캡슐화를 깨뜨린다. 다르게 말하면 상위 클래스가 어떻게 구현되느냐에 따라 하위 클래스의 동작에 이상이 생길 수 있다. 그 여파로 코드 한 줄 건드리지 않은 하위 클래스가 오작동할 수 있다. 이러한 이유로 상위 클래스 설계자가 확장을 충분히 고려하고 문서화도 제대로 해두지 않으면 하위 클래스는 상위 클래스의 변화에 발맞춰 수정되어야 하는 번거로움이 있다.
- 패키지 경계를 넘어 다른 패키지의 구체 클래스를 상속하는 일은 위험하다.
- 상위 클래스에서 제공하는 메서드 구현이 바뀔 경우
- 상위 클래스에서 새로운 메서드가 생길 경우
상속을 잘못 사용한 예
HashSet의 addAll 메서드의 내부 구현에 대해서 알게됨 그냥 갖다쓰면 되는게 아닌.. 이럴 때 캡슐화가 깨지게 된다.
addAll 메서드의 내부 구현이 어떻게 되어있는지 알아야만 정확하게 쓸 수 있다.
하위클래스에서 addAll 메서드를 재정의 하지 않으면 문제는 당장 고칠 수 있으나 다음 릴리즈로 버전이 변경됐을 때 addAll 메서드 내부에서 add를 사용하지 않는다면 우리가 사용하려 했었던 추가된 원소의 갯수가 동작하지 않아서 코드를 수정해야하지만 컴파일단 에러가 나는 것은 아니기 때문에 오류를 발견하기까지 시간이 걸릴 수 있다.
addAll 메서드를 오버라이딩하여 주어진 컬렉션을 반복문 돌려 원소 하나당 add 메서드 한 번만 호출하는 것이다. 이 방식은 HashSet의 addAll을 더 이상 호출하지 않으니 내부 구현 변할 일 없이 원하는 처리가 이루어진다는 점에서 조금 나은 해법이지만 상위 클래스의 메서드 동작을 다시 구현하는 방식은 어렵고, 시간도 더 들고 자칫 오류를 내거나 성능을 떨어뜨릴 수 있다. 또한 하위클래스에서 접근 불가한 private 필드를 써야하는 상황에서는 구현 자체가 불가능하다.
그리고 만약 상위 클래스에서 HashSet에 원소를 추가하는 또다른 메서드가 생겼을 경우 해당 메서드도 오버라이딩 하여 원소 갯수가 몇 개 추가됐는지 로직 구현을 해야하는데 우리가 상위 클래스에 그런 또다른 메서드가 생겼는지 알기는 매우 어려운 일이다.
HashTable과 Vector를 컬렉션 프레임워크로 포함시켰을 이와 관련된 보안 구멍들을 수정해야하는 사태가 있었다.
클래스를 확장하더라도 메서드 재정의 대신 새로운 메서드를 추가하면 괜찮으리라 생각할 수 있지만 다음 릴리스에서 상위 클래스에 새 메서드가 추가됐는데 파필 하위 클래스에 추가한 메서드 시그니처가 같고 반환타입이 다르다면 컴파일 에러가 나며 만약 반환 타입마저 같다면 상위클래스를 재정의한 꼴이나 앞서의 문제와 같은 상황에 부닥친다.
문제는 이뿐만 아니라 의도치 않게 오버라이딩된 새로 만든 메서드는 상위 클래스의 메서드가 요구하는 규약, 검증을 만족하지 못할 가능성이 크다.
- 컴포지션(Composition
- 새로운 클래스를 만들고 private 필드로 기존 클래스의 인스턴스를 참조
- 새 클래스의 인스턴스 메서드들은 기존 클래스에 대응하는 메서드를 호출해 그 결과를 반환한다.
- 기존 클래스의 구현이 바뀌거나, 새로운 메서드가 생기더라도 아무런 영향을 받지 않는다.
데코레이터 패턴(Decorator Pattern)
기존 코드를 변경하지 않고 부가 기능을 추가하는 패턴
- 상속이 아닌 위임을 사용해서 보다 유연하게(런타임에) 부가 기능을 추가하는 것도 가능하다.
데코레이터 패턴(Decorator Pattenr)은 주어진 상황 및 용도에 따라 어떤 객체에 책임(기능)을 동적으로 추가하는 패턴을 말합니다. 데코레이터라는 말 그대로 장식이라고 생각하시면 편합니다. 기본 기능을 가지고 있는 클래스를 하나 만들어주고 추가할 수 있는 기능들을 추가하기 편하도록 설계하는 방식입니다.
Component : 실질적인 인스턴스를 컨트롤하는 역할
ConcreteComponent : Component의 실질적인 인스턴스의 부분으로 책임의 주체의 역할
Decorator : Component와 ConcreteDecorator를 동일시 하도록 해주는 역할
ConcreteDecoreator : 실질적인 장식 인스턴스 및 정의이며 추가된 책임의 주체
예제에서는 Set interface가 Component의 역할, HashSet이 ConcreteComponent, ForwardingSet이 Decorator, InstrumentedSet이 ConcreteDecorator
https://coding-factory.tistory.com/713
콜백 프레임워크와 셀프 문제
콜백 프레임워크와 래퍼를 같이 사용했을 때 발생할 수 있는 문제
- 콜백 함수 : 다른 함수(A)의 인자로 전달된 함수(B)로, 해당 함수(A) 내부에서 필요한 시점에 호출될 수는 함수(B)를 말한다.
- 래퍼로 감싸고 있는 내부 객체가 어떤 클래스(A)의 콜백으로(B) 사용되는 경우에 this를 전달한다면, 해당 클래스(A)는 래퍼가 아닌 내부 객체를 호출한다.(SELF 문제)
Wrapper 클래스 사용시
왜 커피를 마시질 못하니??
wrapper 클래스에서 내부 구현 클래스 메서드가 실행될 때 실제로 작업을 진행하는 객체는 누가 자기를 감싸고 있는지 알 수 없기 때문에 this 는 무조건 자기 자신의 참조값을 넘겨주기 때문에 Decorator 기능을 사용할 수 없는 것..
내부객체는 자신을 감싸고 있는 래퍼의 존재를 모르니 대신 자신(this)의 참조를 넘기고, 콜백 때는 래퍼가 아닌 내부 객체를 호출하게 된다. 이를 SELF 문제라고 한다.
https://www.inflearn.com/course/%EC%9D%B4%ED%8E%99%ED%8B%B0%EB%B8%8C-%EC%9E%90%EB%B0%94-2/dashboard
'개인룸 > 도윤' 카테고리의 다른 글
Item 20. 추상 클래스보다 인터페이스를 우선하라. (0) | 2023.03.13 |
---|---|
Item19. 상속을 고려해 설계하고 문서화하라. 그러지 않았다면 상속을 금지하라 (0) | 2023.03.06 |
Item17. 변경가능성을 최소화 하라 (0) | 2023.02.27 |
Item16. public 클래스에서는 public 필드가 아닌 접근자 메서드를 사용하라 (0) | 2023.02.23 |
Item15. 클래스와 멤버의 접근 권한을 최소화하라. (0) | 2023.02.23 |