19. 상속을 고려해 설계하고 문서화하라. 그러지 않았다면 상속을 금지하라
클래스의 확장이 필요하지 않다면 상속을 금지하라 상속을 허용한다면, 상속을 고려한 설계를 하고 문서를 남겨라
📌 1. 발표 전 알아야 할 개념
상속이 왜 위험한가?
캡슐화가 깨질 가능성이 있다
유지보수가 어렵다
예기치 않은 오류가 발생할 수도 있다다
📕 2. 상속을 고려한 설계와 문서화해라
🔗 전체 예제 코드 : [Item19] 테스트 코드
Car - SportsCar - CarMain 흐름도를 이미지로 나타내면 아래 그림과 같다.
2-1. 상속용 클래스는 재정의할 수 있는 메서드들을 문서로 남겨야 한다
문서에 담겨야 하는 내용들은 다음과 같다.
재정의 가능성이 있는 메서드
어떤 순서로 호출하는지
각각의 호출 결과가 어떤 영향을 미치는지
javadocs(
/**
이나///
로 작성)의@implSpec
을 활용한다
/**
* @implSpec
* drive () method accelerates by calling {@link #accelrate ()}
* The subclass can implement different acceleration methods by redefining Accelrate ().
*/
public final void drive() {
System.out.println("driving");
accelrate();
System.out.println("speed is " + speed);
}
2-2. Hook을 활용하여 유연한 설계를 하자
Hook
이란? 재정의 가능한 메서드protected
를 이용하여 하위 클래스가 동작을 변경할 수 있도록 한다부모 클래스의 중요 내용은 변경하지 않고, 하위 클래스가 확장할 수 있음
// Car 클래스스
/**
* 가속 (protected로 구현된 hoock 메서드)
* 하위 클래스에서 가속 방식 변경 가능
*/
protected void accelrate() {
speed+=30;
System.out.println("accelrate");
}
// SportsCar 클래스
// Car를 상속
public class SportsCar extends Car{
@Override
// accelrate 메서드 재정의
protected void accelrate() {
System.out.println("sportscar accelrate");
super.accelrate();
super.accelrate();
}
//
}
Q. 그럼 어떤 메서드를 protected로 노출해야 할까? A. protected
클래스가 많아지면, 내부 접근 및 구현이 많아지므로 수를 줄여야 한다.
한편? 너무 적게 노출하면, 상속의 의미가 사라진다!
결론은 직접 하위 클래스를 만들어 보는 것만이 유일하다
배포 전에 반드시 하위 클래스를 만들어 검증해야 한다!
2-3. 생성자에서 재정의 가능 메서드를 호출하면 안된다 💥
상위 클래스의 생성자가 하위 클래스의 생성자보다 먼저 실행되므로, 하위클래스에서 재정의한 메서드가 하위 클래스의 생성자보다 먼저 실행된다 ➡️ NullPointerException
따라서 부모 클래스의 생성자에서 재정의 가능 메서드를 호출하면 안됨!
// WrongCar 클래스
private int speed = 0;
public WrongCar() {
System.out.println("Car 생성자");
// 부모 클래스 생성자에서 재정의 메서드를 호출할 경우 오류 발생
accelrate();
}
protected void accelrate() {
System.out.println("accelrate");
speed += 10;
}
// WrongCar를 상속하는 BrokenCar 클래스
public class BrokenCar extends WrongCar{
// 이 위치에서는 초기화 X
private Integer turboSpeed;
public BrokenCar() {
System.out.println("BrokenCar 생성자");
// 초기화
this.turboSpeed = 50;
}
@Override
protected void accelrate() {
System.out.println("BrokenCar accelrate");
// turboSpeed가 초기화 이전이라 오류 발생
setSpeed(getSpeed() + turboSpeed);
}
}
// main 클래스
public class WrongCarMain {
public static void main(String[] args) {
System.out.println("BrokenCar test");
BrokenCar car = new BrokenCar();
car.drive();
}
}
2-4. 상속을 원하지 않는 클래스는 상속을 금지시켜라!
상속은 고려해야 하는 것이 많으므로, 상속용이 아닌 클래스는 아예 상속이 불가능하게 만들어준다.
final
로 선언하기모든 생성자를
private
로 선언하고,public 정적 팩터리 메소드
만들기
public final class newCar {
}
💨 향후 확장 포인트
[템플릿 메서드 패턴(Template Method Pattern)]
전체적인 흐름은 부모 클래스에서 정의
하위 클래스는 hook 메서드를 사용하여 일부만 변경
코드 중복을 줄이고, 확장성을 높이는 패턴
🤖 최종 결론
상속을 고려한 클래스는 문서화(@implSpec)를 하라
Hook을 사용하여 하위 클래스를 유연하게 확장 시킬 수 있게 만들어라
부모 클래스의 생성자에서 재정의 가능한 메서드는 호출하면 안된다
❗어려웠던 점
'재정의 가능한 메서드를 부모 생성자에서 호출하지 마라'라는 말이 어려웠다. 실제 코드를 작성해봤을 때도, 단순히 보면 충분히 정상 작동될 수도 있다고 생각할 수도 있는 코드였다 ➡️ 하지만 상속과 관련된 코드를 작성할 땐, '부모 생성자가 실행되는 동안, 하위 클래스의 필드가 아직 초기화되지 않을 수도 있다'라는 실행 흐름을 정확하게 파악하는 것이 중요하다.
😶🌫️ 느낀점
요약을 다 쓰고 나서 다시 읽어보니, 쉽게 이해할 수 있는 부분들도 너무 어렵게 생각해서 이해 과정에서 오래 걸린 것 같다. 내용을 읽을 때 너무 파고드는 것보다 먼저저 쉬운 예시 코드로 작성해보며 이해력을 높이자.
Last updated