[Effective Java] 17. 상속을 위한 설계와 문서를 갖추거나 아예 금지하라.

2 분 소요

1. 상속을 위한 문서

상속을 위한 문서를 갖춘다는 것은 method를 override 할때 무슨 일이 생기는지 명확하게 남긴다는 것이다.

즉, override가 가능한 메서드를 내부적으로 어떻게 사용하는지 반드시 문서에 남기라는 뜻이다.

관습적으로 override가 가능한 메서드를 어떤식으로 호출하는지는 method 주석문 마지막에 명시하고 보통 This implementation~~로 시작한다.

override 가능한 메서드 란? public 또는 protected로 선언된 비-final 메서드

예로 java.util.AbstractCollection의 remove메서드의 주석문의 마지막 부분이다.

public boolean remove(Object o)

... 

This implementation iterates over the collection looking for the specified element. If it finds the element, it removes the element from the collection using the iterator's remove method.

이 구현은 제거할 원소를 찾아 Collection을 순회한다. 제거할 요소를 찾으면 iterator의 remove 메서드를 사용하여 Collection에서 제거한다.

Note that this implementation throws an UnsupportedOperationException if the iterator returned by this collection's iterator method does not implement the remove method and this collection contains the specified object.

이 Collection의 iterator 메서드가 반환하는 iterator가 remove 메서드를 구현하고 있지 않을 경우 UnsupportedOperationException이 발생할 수 있다.

2. 상속을 위한 문서 자체가 좋지 않다.

좋은 API 문서는 메서드가 하는 일이 무엇인지 설명하는 것이지 그 일을 어떻게 하는것인지 세부 사항을 명시하면 안된다.

하지만 override 가능한 메서드를 설명할때는 위 예와 같이 구현 세부 사항을 설명해야 한다. 이는 결국 상속이 캡슐화 원칙을 침해하기 때문에 발생하는 결과인 것이다.

즉, 상속을 위한 표기때문에 문서가 지저분해 질 수 있다는 점을 기억하자.

3. 효율적인 상위 클래스 설계

책 내용을 잘 모르겠다..

상속에 적합한 클래스는 이를 상속했을때 하위 클래스가 너무 애쓰지 않고 효율적인 하위 클래스를 만들수 있는 설계일 것이다.

이렇게 효율적인 하위 클래스를 작성할 수 있게 하려면 클래스 내부 동작에 개입할 수 있는 hooks을 신중하게 고르고 이를 protected 메서드 형태로 제공해야 한다.

상속을 고려한 class를 설계할때 protected로 선언할 멤버는 어떻게 결정할까? 이를 위한 좋은 방법은 따로 없다. 그저 신중히 고려해보고 실제로 하위 클래스를 만들어 테스트해보는것 뿐이다.

참고로 protected 멤버는 가능한 줄여야 하는데 이게 구현 세부사항에 대한 일종의 서약과 같은 구실을 하기 때문이다. protected가 너무 없는것도 주의해야 한다. 이는 상속하여 사용하기에 좋지않은 클래스일수도 있기 때문이다.

4. 상속을 허용할때 제약사항

4.1. 생성자가 override 가능한 메서드를 호출해서는 안된다.

이를 위반하면 프로그램에서 오류가 발생할 수 있다.

public class Super {
	public Super() {
		overrideMe();
	}
	
	public void overrideMe(){ }
}

public class Sub extends Super {
	private final Date date;
	public Sub() {
		this.date = new Date();
	}
	
	public void overrideMe() {
		System.out.println(date);
	}
	
	public static void main(String[] args) [
		Sub sub = new Sub();
		sub.overrideMe();
	}
}

위 내용을 실행하면 null이 한번 출력되고 날짜가 한번 출력되어 총 2번이 출력된다.

Sub 객체 생성을 위해 생성자가 불리면 먼저 상위 클래스인 Super의 생성자가 먼저 호출되는데, Super 내부에서 overrideMe()를 호출하고 있다.
이 때 자식 클래스가 overrideMe를 override하여 구현하고 있어 System.println(date); 가 실행되는데 이 때는 date 객체가 초기화되지 않은 상태이다. 따라서 null이 출력된다.

위 예와 같이 생성자가 override 가능한 메서드를 사용하면 nullpointException과 같은 버그가 발생할 수 있다.

4.2. clone, readObject 메서드안에서 override 가능한 메서드를 호출해서는 안된다.

이는 추후 다시 봐야할듯함.

5. 결론

상속을 위한 클래스를 설계하면 클래스 자체에 상당히 많은 제약이 가해지니 가능한 상속은 배제하는것이 좋다.

만약 일반적인 객체 생성 가능한 클래스 라면.. 보통 final로 선언되어 있지도 않고 하위 클래스 구현을 위한 문서를 갖추고 있지 않다.

이러한 클래스는 굳이 상속을 하여 하위 클래스를 만들지 말아야 할 것이다.

댓글남기기