상속은 자식이 부모의 기능을 물려받는 것이다. 하지만 extends를 사용한 만큼 단순히 물려 받았다 보단 자식클래스는 부모클래스로 부터 확장하겠다는 것까지 이해하면 좋겠다.
public class Cat {
...
}
public class Lion extends Cat {
...
}
고양이 클래스가 있고 사자 클래스는 고양이 클래스를 상속 받았다. 현실에서도 사자는 고양이과로 고양이의 특성을 지니고 있다. 이 말은 고양이가 하는건 사자도 할 수 있다는 거다. 그리고 사자는 고양이로부터 확장된 것 이라고 이해하면 된다.
상속 관계를 나타내는 용어다. 사자 is a 고양이라고 사용하며 사자가 고양이를 상속 받았다고 볼 수 있다.
합성은 객체에게 없는 기능을 다른 객체에게 얻어와서 의지하는 것이다.
public class Computer{
private Cpu cpu;
public Computer(Cpu cpu) {
this.cpu = cpu;
}
public int add(int a, int b){
return cpu.add(a,b);
}
public static void main(String[] args) {
Computer com = new Computer(new Cpu());
System.out.println(com.add(1, 2)); //3
}
}
1+2를 계산하고 싶어서 Computer 객체의 add() 메소드를 실행시켰다. 근데 Computer클래스의 add()를 보면 더하기는 안하고 Cpu객체의 add()만 실행 시켰고 실제 계산은 Cpu객체의 add()메소드에서 이루어 진다.
현실에서도 컴퓨터는 여러 부품을 모아 만들어진 것이며, 컴퓨터 자체에는 연산 능력이 없고 CPU가 연산기능을 한다. 때문에 컴퓨터는 cpu를 의존한다고 말할 수 있고 코드에서도 add()명령을 받은 Computer 객체는 그 일을 CPU객체에게 의존하고 있는 거다.
의존의 관계를 나타내는 용어다. 컴퓨터 has a CPU라고 사용하고 컴퓨터는 CPU를 가지고 있다(의존한다)라고 한다.
상속관계를 명확하게 할 수 없으면 합성을 사용하는 게 좋다. 잘 못된 상속은 캡슐화를 위반 하게 되며 명확한 상속관계는 SOLID원칙의 리스코프 치환 원칙을 따라 상속받은 자식 클래스는 부모 클래스를 대체할 수 있는 경우에만 사용하는게 좋다.
잘 못된 상속의 예시는 JAVA에서도 찾아볼 수 있다.
public class Composite {
public static void main(String[] args) {
Stack<String> stack = new Stack<>();
stack.push("1st");
stack.push("2nd");
stack.push("3rd");
stack.add(0, "4th");
System.out.println(stack.pop()); // ???
}
}
Stack은 LIFO(후입선출)구조의 자료구조다. 근데 Stack클래스는 Vector클래스를 상속 받고 있다. Vector는 List의 기능이라고 생각하면 되는데 이게 왜 잘 못일까?
코드를 보면 stack.add(0, "4th")부분은 Vector에서 상속받은 기능이다. 하지만 stack은 LIFO로 늦게 들어온 데이터가 항상 먼저 나가야하는데 중간에 데이터를 삽입한다는건 옳지 못하다. 때문에 stack.pop()하면 3rd가 나올 것 이다.
Stack은 add()의 기능이 사실상 필요가 없지만 상속받은 기능인 add()를 노출해서 사용 할 수 있다. 이건 캡슐화를 위한반 예시이다.
한 줄평 : 코드의 재사용을 위한 상속은 캡슐화를 위한할 수도 있으니 합성을 선택해보자.
참고 -
https://incheol-jung.gitbook.io/docs/q-and-a/architecture/undefined-2#undefined-8