본문 바로가기

Programming/기초지식

[객체 지향] 상속보단 조립

반응형

상속을 통한 기능 재사용 시 다음과 같은 문제가 발생할 수 있다.

  • 상위 클래스 변경이 어렵다.
  • 클래스가 증가한다.
  • 상속을 오용할 수 있다.

 

상위 클래스의 변경이 어렵다.

상속 관계에서 상위 클래스가 변경되면 상위 클래스를 상속한 하위 클래스들에 영향을 미친다.

상위 클래스 입장에서 앞으로 어떤 하위 클래스가 추가될지 알 수 없기 때문에 하위 클래스가 많아질수록 상위 클래스의 변경이 어려워진다. 

또한 상위 클래스가 어떻게 동작하는지 잘 숙지하고 있어야 하위 클래스가 기능을 재사용할 수 있는데, 이는 상위 클래스는 하위 클래스에 대한 캡슐화(기능을 드러내고 구현을 감춤)가 약해진다고 할 수 있다.

 

클래스가 증가한다.

프로젝트 규모가 커지고 기능이 추가될 수 있다. 이런 변화에 따라 새로운 기능 추가/구현을 위해 클래스가 증가될 수 있다. 이는 프로젝트 구조를 복잡하게 만든다.

 

상속을 오용할 수 있다.

예를 들어 Luggage 클래스가 있는데, 자바 컬렉션 프레임워크의 ArrayList에 클래스의 타입 파라미터로 사용한다고 가정해보자. 

public class Container extends ArrayList<Luggage> { 
    ... 
}

이런 상황에서 사용자가 Luggage 클래스에 정의된 add() 메소드를 사용해야 한다고 했을 때 ArrayList() 클래스의 메서드인 add()를 잘못 사용해 논리적 오류가 발생할 가능성이 높아지게 된다.

 

상속보단 조립

 

조립(Composition)

  • 여러 객체를 묶어서 더 복잡한 기능을 제공.
  • 보통 필드로 다른 객체를 참조하는 방식으로 조립 또는 객체를 필요한 시점에 생성하여 사용.

 

 

public class FlowController {
    private Enctyptor enctyptor = new Enctyptor(); // 필드로 조립
    
    public void process() {
        ...
        byte[] enctyptedData = enctyptor.encrypt(data);
    }
}

 

위 코드를 보면 암호화 기능이 필요해서 암호화 기능을 제공하는 Enctyptor 클래스를 상속받지 않고 필드나 메소드 형태로 Encryptor 객체를 생성하고 재사용하고 있다. 즉 다양한 기능을 제공하는 객체를 조립하여 더 복잡한 기능을 제공한다.

 

// 상속
public class Container extends ArayList<Luggage> {
    private int maxSize;
    private int currentSize;
    
    ...
    
    public void put(Luggage lug) {
       if(!canContain(lug)){
          throw new NotEnoughSpaceException();
       super.add(lug);
       currentSize += lug.size();
       }
    }
}

// 조립
public class Container {
    private int maxSize;
    private int currnetSize;
    
    // Container 클래스는 더 이상 ArrayList의 add() 메소드를 제공하지 않기 때문에
    // 앞서 살펴본 '불필요한 기능을 상속하여 발생한 문제'를 방지할 수 있다.
    private List<Luggage> luggages = new ArrayList();
    
    ...
    
    public void put(Luggage lug) {
        if(!canContain(lug)){
            throw new NotEnoughSpaceException();
        luggages.add(lug);
        }
    }
}

 

어떤 클래스의 기능을 재사용하기 위해 상속을 고려하고 있다면 우선 조립하는 방식으로 풀 수 없는지 검토해보자.

진짜 하위 타입인 경우에만 상속을 사용한다. 예를 들어 아래와 같이 Container 클래스의 경우 Container는 ArrayList의 한 종류가 아니다. 단지 '목록 관리'라는 기능을 원활히 구현하기 위해 ArrayList를 상속했을 뿐이다. 이렇게 진짜 하위 타입이 아니고 단지 기능 재사용을 위해 상속을 오용하면 문제가 발생할 수 있다.

 

public class Container extends ArrayList<Luggage> { 
    ... 
}

 

상속은 하위 타입이 상위 타입의 동작 구조에 매우 밀접하게 엮이는 경향이 있다는 것이다. 따라서 상위 타입의 구조가 변경되면 하위 타입에 영향을 줄 가능성이 높다.(하위 타입에 대한 상위 타입의 캡슐화가 약해진다)

 

조합은 제공하는 기능을 사용하므로 기능의 내부 구현이 변경되더라도 영향을 받을 가능성이 줄어든다.

 

참고

https://www.inflearn.com/course/%EA%B0%9D%EC%B2%B4-%EC%A7%80%ED%96%A5-%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%B0%8D-%EC%9E%85%EB%AC%B8/dashboard

 

객체 지향 프로그래밍 입문 - 인프런 | 강의

잘 하는 개발자가 되기 위해서는 유연한 코드를 작성할 줄 알아야합니다. 객체 지향을 이용해서 변경하기 좋은 유연한 코드를 만드는 방법을 알아보세요., 객체 지향 프로그래밍 입문 이번 '객

www.inflearn.com

반응형