티스토리 뷰
728x90
반응형
상속보다는 컴포지션을 사용하라
- 여기서 말하는 상속은 클래스가 다른 클래스를 확장하는 상속을 말합니다. 그렇기 때문에 클래스가 인터페이스를 구현하거나 인터페이스가 다른 인터페이스를 확장하는 상속과는 무관합니다.
- 상속을 사용하게 되면 메서드 호출과 달리 캡슐화를 깨뜨립니다. 다르게 말하면 상위 클래스가 어떻게 구현되느냐에 따라 하위 클래스의 동작에 이상이 생길 수 있으며, 상위 클래스는 릴리즈마다 내부 구현이 달라질 수 있어서 그 여파로 하위 클래스가 오작동할 수 있습니다.
- 컴포지션이란 다른 클래스에서 상위 클래스를 private 필드로 작성해 참조하도록 하여 기존 상위 클래스가 다른 클래스의 구성 요소로 쓰인다는 의미입니다.
🧨 문제가 발생하는 예제
- 아래 예제를 실행 했을 때 결과는 3이 나올것 같지만 실제 결과는 6이 출력되게 됩니다.
- HashSet 클래스의 addAll 메서드는 내부적으로 add 메서드를 호출하게 됩니다. 그렇기 때문에 addCount++이 호출되면서 6이 출력되게 되었습니다. 이런 내부 구현 방식은 HashSet 문서에도 당연히 없기 때문에 아래와 같은 문제는 addAll 메서드를 재정의 하지 않으면 고칠 수 있는 문제입니다.
@Getter
public class InstrumentedHashSet<E> extends HashSet<E> {
private int addCount = 0;
public InstrumentedHashSet() {}
public InstrumentedHashSet(int initialCapacity, float loadFactor) {
super(initialCapacity, loadFactor);
}
@Override
public boolean add(E e) {
addCount++;
return super.add(e);
}
@Override
public boolean addAll(Collection<? extends E> c) {
addCount += c.size();
return super.addAll(c);
}
}
public class EffectiveJavaApplication {
public static void main(String[] args) throws Exception {
InstrumentedHashSet<String> set = new InstrumentedHashSet<>();
set.addAll(List.of("hello", "java", "hard"));
System.out.println(set.getAddCount()); // 6
}
}
💡 컴포지션을 사용하자.
- 위와 같은 문제들은 새로운 클래스에서 해당 클래스를 private 필드로 작성해 참조하도록 합니다. 이를 기존 클래스가 새로운 클래스의 구성요소로 쓰인다는 의미로 컴포지션이라고 합니다.
- 새로운 클래스에서 기존 클래스에 대응하는 메서드를 호출하면 새로운 클래스는 기존 클래스의 메서드를 호출해서 결과를 반환하는데 이를 전달이라고 하며, 이런 새로운 클래스의 메서드를 전달 메서드라고 부릅니다.
@Getter
public class CompositionInstrumentedSet<E> extends ForwardingSet<E> {
private int addCount = 0;
public CompositionInstrumentedSet(Set<E> s) {
super(s);
}
@Override
public boolean add(E e) {
addCount++;
return super.add(e);
}
@Override
public boolean addAll(Collection<? extends E> c) {
addCount += c.size();
return super.addAll(c);
}
}
public class ForwardingSet<E> implements Set<E> {
private final Set<E> s;
public ForwardingSet(Set<E> s) { this.s = s; }
@Override
public int size() { return 0; }
@Override
public boolean isEmpty() { return false; }
@Override
public boolean contains(Object o) { return false; }
@Override
public Iterator<E> iterator() { return null; }
@Override
public Object[] toArray() { return new Object[0]; }
@Override
public <T> T[] toArray(T[] a) { return null; }
@Override
public boolean add(E e) { return false; }
@Override
public boolean remove(Object o) { return false; }
@Override
public boolean containsAll(Collection<?> c) { return false; }
@Override
public boolean addAll(Collection<? extends E> c) { return false; }
@Override
public boolean retainAll(Collection<?> c) { return false; }
@Override
public boolean removeAll(Collection<?> c) { return false; }
@Override
public void clear() { }
}
public class EffectiveJavaApplication {
public static void main(String[] args) throws Exception {
CompositionInstrumentedSet<String> set = new CompositionInstrumentedSet<>(new HashSet<>());
set.addAll(List.of("hello", "java", "hard"));
System.out.println(set.getAddCount()); // 3
}
}
💡 정리
- 상속을 캡슐화를 깨트립니다.
- 상속은 상위 클래스와 하위 클래스의 관계가 온전한 is-a 관계일 때만 사용해야 합니다.
- is-a 관계라 할지라도 하위 클래스의 패키지가 상위 클래스의 패키지와 다르고, 상위 클래스가 확장을 고려하지 않은 경우에는 문제가 발생할 수 있습니다.
728x90
반응형
'스터디 > 이펙티브 자바' 카테고리의 다른 글
이펙티브 자바 - Item20. 추상 클래스보다는 인터페이스를 우선하라 (0) | 2022.07.15 |
---|---|
이펙티브 자바 - Item19. 상속을 고려해 설계하고 문서화하라. 그렇지 않다면 상속을 금지하라. (0) | 2022.07.13 |
이펙티브 자바 - Item17. 변경 가능성을 최소화하라. (0) | 2022.07.12 |
이펙티브 자바 - Item16. public 클래스에서는 public 필드가 아닌 접근자 메서드를 사용하라. (0) | 2022.07.11 |
이펙티브 자바 - Item15. 클래스와 멤버의 접근 권한을 최소화하라. (0) | 2022.07.11 |
공지사항
최근에 올라온 글
최근에 달린 댓글
- Total
- Today
- Yesterday
TAG
- pipe and filter architecture
- java ThreadLocal
- 자바 백엔드 개발자 추천 도서
- transactional outbox pattern
- pipeline architecture
- 람다 표현식
- 서비스 기반 아키텍처
- java userThread와 DaemonThread
- transactional outbox pattern spring boot
- 공간 기반 아키텍처
- spring boot excel download paging
- spring boot excel download oom
- spring boot redisson destributed lock
- redis sorted set
- spring boot redisson 분산락 구현
- spring boot redisson sorted set
- space based architecture
- spring boot 엑셀 다운로드
- service based architecture
- 트랜잭셔널 아웃박스 패턴 스프링부트
- JDK Dynamic Proxy와 CGLIB의 차이
- @ControllerAdvice
- spring boot redis 대기열 구현
- 트랜잭셔널 아웃박스 패턴 스프링 부트 예제
- redis sorted set으로 대기열 구현
- redis 대기열 구현
- spring boot poi excel download
- microkernel architecture
- polling publisher spring boot
- 레이어드 아키텍처란
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |
글 보관함