재사용: 상속보단 조립

객체지향과 디자인 패턴(최범균 저) 재사용: 상속보단 조립 파트 정리한 내용입니다.

재사용 : 상속보단 조립

객체 지향의 주요 특징으로 재사용을 말하면서 그 예로 상속을 드는 경우가 있다. 물론, 상속을 사용하면 상위 클래스에 구현된 기능을 그대로 재사용할 수 있기 때문에 상속을 사용하면 재사용을 쉽게 할 수 있는 것은 분명하다.

상속을 통한 재사용의 단점

  • 상위 클래스 변경의 어려움

    어떤 클래스를 상속받는다는 것은 그 클래스에 의존한다는 뜻이다. 따라서 의존하는 클래스의 코드가 변경되면 영향을 받을 수 있다는 것이다.

    상속 계층을 따라 상위 클래스의 변경이 하위 클래스에 영향을 주기 떄문에, 최악의 경우 상위 클래스의 변화가 모든 하위 클래스에 영향을 줄 수 있다.

  • 클래스의 불필요한 증가

    유사한 기능을 확장하는 과정에서 클래스의 개수가 불필요하게 증가할 수 있다.

  • 상속의 오용

    잘못된 상속으로 인한 잘못된 메서드를 제공할 수 있다.

    public class Container extends ArrayList<Luggage> {
    	private int maxSize;
    	private int currentSize;
    
    	public Container(int maxSize) {
    		this.maxSize = maxSize;
    	}
    
    	public void put(Luggage lug) throws NotEnoughSpaceException { ... }
    	public void extract(Luggage lug) { ... }
    	public boolean canContain(Luggage lug) { ... }
    }

    Container 클래스에 정의된 세 개의 메서드 뿐만 아니라 상위 클래스인 ArrayList 클래스에 등록된 메서드의 목록을 함께 제공된다.

    ArrayList의 add() 메서드를 사용하면 Container의 여분 계산이 정상적으로 동작하지 않기 때문에 에러가 발생할 것이다.

    이건 누구의 잘못일까? Put() 메서드를 사용하지 않고 add() 메서드를 사용한 개발자의 잘못일까? 물론, 잘못은 Container 클래스의 사용법을 제대로 지키지 않은 개발자에 있다. 하지만, 더 큰 잘못은 오용의 여지를 준 Container 클래스 작성자에 있다.

    위와 같은 문제가 발생하는 이유는 Container는 사실 ArrayList가 아니기 때문이다. 상속은 IS-A 관계가 성립할 때에만 사용해야 하는데 “Container는 ArrayList이다.” 라는 IS-A 관계가 성립되지 않는다.

조립을 이용한 재사용

객체 조립은 여러 객체를 묶어서 더 복잡한 기능을 제공하는 객체를 만들어내는 것이다.

한 객체가 다른 객체를 조립해서 필드로 갖는다는 것은 다른 객체의 기능을 사용한다는 의미를 내포한다.

앞서 기능이 추가될 때마다 Storage 클래스를 상속받은 하위 클래스가 증가했던 방식과 비교해 봤을 때, 조립을 이용하면 불필요한 클래스 증가를 방지할 수 있다는 것을 알 수 있다.

조립 방식의 또 다른 장점은 런타임에 조립 대상 객체를 교체할 수 있다는 것이다.

상속에 비해 조립을 통한 재사용의 단점은 아래 그림 에서 보는 것처럼 상대적으로 런타임 구조가 복잡해진다는 것이다. 또 다른 단점은 상속보다 구현이 더 어렵다는데 있다.

그렇다면 상속은 언제 사용해야 할까?

  • 상속을 사용할 때에는 재사용이라는 관점이 아닌 기능의 확장이라는 관점에서 상속을 적용해야 한다.

  • 이처럼 상속은 명확한 IS-A 관계에서 점진적으로 상위 클래스의 기능을 확장해 나갈 때 사용할 수 있다. 단, 최초에는 명확한 IS-A 관계로 보여서 상속을 이용해서 기능을 확장했다고 하더라도, 이후에 클래스의 개수가 불필요하게 증가하는 문제가 발생하거나 상위 클래스의 변경이 어려워지는 등 상위 클래스를 상속받을 때의 단점이 발생한다면, 조립으로 전환하는것을 고려해야 한다.

Last updated