냄새 23. 상속포기

    들어가기 전

    이 글은 인프런 백기선님의 강의를 복습하며 작성한 글입니다.


    냄새 23. 상속 포기 (Refused Bequest)

    • 상속 포기 냄새
      • 서브 클래스가 슈퍼 클래스에서 제공하는 메서드나 데이터를 잘 활용하지 않는다는 것은 해당 상속 구조에 문제가 있음을 의미함. 즉, 슈퍼 클래스에서 제공하는 기능이 공통 기능에 어울리지 않음을 의미함. 
    • 리팩토링 방법
      • 기존의 서브클래스 또는 새로운 서브 클래스를 만들고 슈퍼클래스에서 '메서드와 필드를 내려주면 (Push Down Method / Field)' 슈퍼클래스에 공통으로 사용하는 기능만 남길 수 있다.
      • 서브클래스가 슈퍼클래스의 기능을 재사용하고 싶지만, 인터페이스를 따르고 싶지 않은 경우에는 '슈퍼클래스 또는 서브클래스를 위임으로 교체하기' 리팩토링을 적용할 수 있다. 

     

    상속포기란?

    슈퍼 클래스에서 제공하는 메서드 / 데이터를 서브 클래스가 적극적으로 활용하지 않는 경우가 있다. 서브 클래스가 슈퍼 클래스의 유산 중 일부만 사용하는 경우인데, 이 경우를 '상속 포기 냄새'로 볼 수 있다. 이런 경우에는 서브 클래스에서 가지고 있어야 할 필드 / 메서드를 상위 클래스가 가지고 있기 때문에 발생하는 문제다. 즉, 슈퍼 클래스가 제공할 '공통적인 기능'이라기 보다는 각 서브 클래스의 '특별한 기능'이기 때문이다.

    이런 냄새를 해결하는 방법은 상위 클래스에 있는 필드 / 메서드를 해당하는 하위 클래스로 내려주면 된다. 만약 적절한 하위클래스가 없다면, 새로운 하위 클래스를 만들어서 그쪽으로 기능을 내려주면 된다. 


    슈퍼 클래스 인터페이스 따르지 않고, 슈퍼 클래스 기능을 재사용하고 싶다면? 

    슈퍼클래스가 제공하는 메서드를 재사용하고 싶다면 서브 클래스는 슈퍼 클래스가 제공하는 메서드의 규약을 지켜야한다. 예를 들어 어떤 메서드는 '할당량을 리턴하는 메서드다' 라고 정의해두었는데, 서브 클래스는 할당량이 아니라 '내가 한만큼의 수량을 리턴하고 싶다' 같은 경우가 있을 수 있다. 이런 경우에는 슈퍼 클래스의 규약 / 서브 클래스의 규약이 달라지기 때문에 서브 클래스를 사용하는 것이 적절하지 않다.

    이 경우, '서브 클래스를 슈퍼 클래스를 위임으로 교체하기'로 처리할 수 있다. 슈퍼 클래스의 기능을 일부는 사용하지만, 슈퍼 클래스와 전혀 다른 인터페이스/규약을 따르도록 변경된다. 


    코드 

    슈퍼클래스인 Employee 클래스에는 할당량 (Quota)가 있다. 그리고 Employee 클래스는 SalesMan, Enginner 클래스가 상속한다. 그런데 Enginner 클래스는 Quota 필드가 필요하지 않다. Quota 필드는 SalesMan에게 특화된 필드이기 때문이다.

    따라서 Enginner 관점에서는 Employee의 Quota 필드에 대해서 상속 포기를 한다. 이런 냄새가 발생한 것은 특화된 기능인 Quota가 공통 로직으로 슈퍼 클래스에 올라왔기 때문이다. 따라서 Quota 필드를 특정 하위 클래스로만 옮겨서 '상속 포기 냄새'를 제거한다.  

     

    // Employee의 상속 클래스는 Engineer / SalesMan이 있음.
    // Engineer에게 Quota라는 필드는 적절하지 않다. 
    // Quota라는 필드는 SalesMan에만 적절하기 때문에 Enginner는 Employee를 상속받으면, Quota 필드에 대해서는 사실상 상속포기를 한다. 
    // 상속포기는 불분명한 냄새를 풍기기 때문에 공통 속성만 상위에 남기고, 필요한 것들은 하위로 옮겨준다.
    public class Employee {
    
        protected Quota quota;
        protected Quota getQuota() {
            return new Quota();
        }
    }
    
    public class Engineer extends Employee {}
    public class Salesman extends Employee {}

    인텔리제이의 Push Members down 리팩토링 기능을 이용해서 해당 멤버를 SalesMan으로만 옮겨주면 된다. 리팩토링 결과는 다음과 같다.

    // Employee에 있던 Quota는 SalesMan으로만 넘겨주었다.
    public class Employee {}
    
    public class Engineer extends Employee {}
    
    public class Salesman extends Employee {
    
        protected Quota quota;
        protected Quota getQuota() {return new Quota();
    }

    고민해볼만한 점

    상속 포기를 해결하기 위해서는 코드 중복이 늘어날 수 있다. 예를 들어 10개의 하위 클래스가 있는데, 8개에서만 사용하는 필드라면 이것은 하위 클래스에 있어야 할까? 공통 클래스에 있어야 할까? 

     

     

     

     

     

    댓글

    Designed by JB FACTORY