[리팩토링] 악취 24 : 상속 포기

 

✍️ 악취 24 : 상속 포기

서브 클래스에 필드와 메서드가 빈약하다면 서브 클래스가 가져야 할 필드나 메서드를 슈퍼클래스에서 가지고 있을 확률이 크다. 서브 클래스를 제거하거나 서브 클래스로 '필드/메서드 내려주기'를 적용하면 문제는 해결된다.


만약 서브 클래스가 슈퍼 클래스의 기능은 재사용 하고 싶지만 인터페이스가 가진 규약은 따르고 싶지 않다면 '슈퍼 클래스 또는 서브 클래스를 위임으로 변경하기'를 고려해야 한다. 

 

여기 상속 포기 악취를 해결하기 위한 두 가지 리팩토링 기법이 있다.

1. '필드/메서드 내려주기'

2. '슈퍼 클래스 또는 서브 클래스를 위임으로 변경하기'

 

🍊 필드/메서드 내려주기

슈퍼 클래스에서 할당량(Quota)를 선언했고 서브 클래스가 이를 상속을 받는 구조이다.

public class Employee {

    protected Quota quota;

    protected Quota getQuota() {
        return new Quota();
    }

}

public class Engineer extends Employee {

}

public class Salesman extends Employee {

}

 

Salesman만 할당량(Quota)을 필요로 한다면 슈퍼 클래스 과도한 책임이므로 필드와 메서드를 내려준다.

public class Employee {

}

public class Salesman extends Employee {

    protected Quota quota;

    protected Quota getQuota() {
        return new Quota();
    }
}

public class Engineer extends Employee {

}

 

🌱 슈퍼 클래스 또는 서브 클래스를 위임으로 변경하기

슈퍼 클래스가 가진 기능을 재사용하고 싶지만 인터페이스가 가진 규악은 따르고 싶지 않다면 상속 구조를 위임으로 변경할 수 있다. List를 상속받아 구현한 Stack vs List를 위임해서 구현한 Stack이 그 대표적인 예시이다.


List의 get은 인덱스 기반으로 동작하는 반면 Stack의 peek은 최상위 엘리먼트에 대해서 동작한다. Stack 입장에선 List가 제공하는 기능을 재사용하고 싶지만 peek만 보더라도 본래의 기능적 규약을 위반했다. 

public class SimpleList {

    protected List<Integer> list = new ArrayList<>();

    public boolean add(int value) {
        return list.add(value);
    }

    public int get(int index) {
        return list.get(index);
    }
    
    ...
}

public class SimpleStack extends SimpleList {

    ...
    
    public int peek() {
        if (isEmpty())
            throw new RuntimeException();

        return get(size() - 1);
    }

    public int pop() {
        if (isEmpty())
            throw new RuntimeException();

        return remove(size() - 1);
    }
}

 

인터페이스가 가진 기능적 규약을 지키기 어렵다면 과감히 상속을 버리고 위임으로 변경하는 것을 고려해야 한다.

public class SimpleStack {

    private SimpleList list = new SimpleList();

    ...
    
    public int peek() {
        if (list.isEmpty())
            throw new RuntimeException();

        return list.get(size() - 1);
    }

    ...
}