본문 바로가기

JAVA/Effective Java

item 18) 상속보다는 컴포지션을 사용하라

728x90

1. 상속 vs 컴포지션

 

상속 : 하위 클래스가 상위 클래스의 특성을 재정의한 것 (IS-A 관계) 

( 여기서 상속은 클래스가 인터페이스를 구현하거나 인터페이스가 다른 인터페이스를 확장하는 개념 X )

 

컴포지션 : 기존 클래스가 새로운 클래스의 구성요소가 되는 것 위임한다.(delegation, HAS-A 관계)

 

- 상속은 캡슐화를 꺠트리고, 상위 클래스에 의존성 관계를 가지고 있기 때문에 상위클래스의 변화에 따라 하위클래스가 변경될 가능성이 있다. (OCP 규칙에 위반)

- 메소드는 언제든지 재정의될 가능성이 있음

- 다른 패키지의 구체클래스를 상속하는 일은 위험하고, 같은 패키지 내에서라도 문제를 야기할수 있다.

- 보통 IS-A 관계일떄 상속을 사용하고, HAS-A 관계일때 컴포지션을 사용하라고 하지만 이는 잘못된 것( But 관점의 차이일뿐임 

CAR IS-A AUTOMOBILE 이기도 하지만, CAR HAS-A ENGINE, WHEEL 관계이기도 함)

 

2. 상속이 야기한 문제

 

다음과 관계 상황을 생각해보자

 

Cat은 meow 와 eat를 한다.

Dog은 bark와 eat를 한다.

공통된 eat를 추출하여 이를 상속하는 관계를 만들었다.

 

CleaningRobot은 move와 clean를 한다.

KillingRobot은 move와 kill를 한다.

마찬가지로 공통된 move를 추출하여 이를 상속하는 관계를 아래와 같이 만들었다.

 

class Animal{

	void eat();
}

class Cat extends Animal{
	void meow();
}

class Dog extends Animal{
	void bark();
}


class Robot{
	void move();
}

class CleaningRobot extends Robot{
	void clean();
}


class KillingRobot extends Robot{
	void kill();
}

시간이 지나 클라이언트의 요구사항이 바뀌었다.

KillingRobotDog를 만들어달라는 것이다. 이 객체는 bark, kill, move를 할수는 있지만 eat를 하지는 못하는 객체다.

위와 같은 구현은 요구사항이 바뀐 상황에 대처하지 못한다. 이는 우리가 Dog를 만들때 eat를 무조건 한다고 가정을 해버렸기 때문에 이러한 상황에 유연하게 대처하지 못하는 것이다. 임시방책으로 비어있는 eat를 만드는 방법을 사용할수도 있지만, 쓰지도 않을 함수를 만들게 되고,  코드의 유지보수성 또한 떨어트리게 된다.

이렇듯 상속은 객체의 특성을 단정지어버리기 떄문에, 미래에 일어날수 있는 변화에 유연하게 대처하지 못한다.

 

아래코드는 똑같은 관계를 컴포지션 관계로 구현한것이다.

bark, eat ,clean ,move를 하는 행동들을 객체화 시켰고, 각 객체에 맞게 이를 위치시키는 HAS-A 관계로 바꾸었다. 이로 인해 클라이언트의 요구사항이 바뀌더라도 유연하게 대처할수있게 되었다. 또한 행동의 구현체가 바뀌더라도 인터페이스가 그대로 남아있기 때문에 변화를 줄 필요가 없다. 즉, 확장에 열려있고 변화에는 닫혀있는 구조이다. (OCP 원칙)

 

class Dog{
	BarkingStrategy bark;
    EatingStrategy eat;
}

class ManyEatingDog{
	BarkingStrategy bark;
    EatingStrategy eat;// eat 구현체를 많이 먹는 것으로 갈아낀다 !
}

class Cat{
	MeowingStrategy meow;
    EatingStrategy eat;
}

class CleaningRobot{
	CleaningStrategy clean;
    MovingStrategy move;
}

class KillingRobot{
	KillingStrategy kill;
    MovingStrategy move;
}


class KillingRobotDog  {
	KillingStrategy kill;
    MovingStrategy move;
    BarkingStrategy bark;
}

 

3.대표적인 자바 API의 잘못된 상속 관계

 

  • IS-A 관계가 확실한 경우에만 상속을 사용할 것 

Stack 은 Vector가 아닌데 상속했다.

Properties는 HashTable이 아닌데 상속했다.

이 관계를 표현하는데 컴포지션으로 사용했으면 더 좋았을 것이다.

public class Stack<E>
extends Vector<E>

public class Properties
extends Hashtable<Object,Object>