Composition vs Inheritance은 자주 묻는 면접 질문 중 하나입니다. 또한 Composition을 Inheritance보다 사용하는 것이 좋다고 들어보셨을 것입니다.
Composition vs Inheritance
Composition과 Inheritance는 모두 객체 지향 프로그래밍 개념입니다. 이들은 Java와 같은 특정 프로그래밍 언어에 결합되어 있지 않습니다. 우리가 프로그램적으로 Composition을 Inheritance보다 선호하는 이유를 비교하기 전에, 간단한 정의를 살펴보겠습니다.
Composition
Composition은 객체 지향 프로그래밍에서 객체 간의 has-a 관계를 구현하는 설계 기술입니다. Java에서 Composition은 다른 객체의 인스턴스 변수를 사용하여 달성됩니다. 예를 들어 Java 객체 지향 프로그래밍에서 Job을 가진 사람은 다음과 같이 구현됩니다.
package com.journaldev.composition;
public class Job {
// 변수, 메서드 등
}
package com.journaldev.composition;
public class Person {
//composition has-a relationship
private Job job;
// 변수, 메서드, 생성자 등. 객체 지향
Inheritance
상속은 객체 지향 프로그래밍에서 is-a 관계를 구현하는 설계 기술입니다. Java에서는 extends 키워드를 사용하여 상속을 구현합니다. 예를 들어, Java 프로그래밍에서 Cat은 Animal 관계를 아래와 같이 구현할 것입니다.
package com.journaldev.inheritance;
public class Animal {
// 변수, 메서드 등.
}
package com.journaldev.inheritance;
public class Cat extends Animal{
}
상속보다는 구성(Composition)
구성과 상속은 각기 다른 방식으로 코드 재사용을 촉진합니다. 그래서 어떤 것을 선택해야 할까요? 구성과 상속을 비교하는 방법은 무엇일까요? 프로그래밍에서는 상속보다는 구성을 선호해야 한다는 말을 들어보셨을 것입니다. 몇 가지 이유를 살펴보면서 구성과 상속을 선택하는 데 도움이 될 것입니다.
-
상속은 엄밀히 결합되어 있지만, 구성은 느슨하게 결합되어 있습니다. 아래 클래스가 상속을 사용하는 경우를 가정해 봅시다.
package com.journaldev.java.examples; public class ClassA { public void foo(){ } } class ClassB extends ClassA{ public void bar(){ } }
간단히 하기 위해, 우리는 슈퍼클래스와 서브클래스를 하나의 패키지에 넣었습니다. 그러나 대부분의 경우에는 서로 다른 코드베이스에 있을 것입니다. 슈퍼클래스 ClassA를 확장하는 많은 클래스가 있을 수 있습니다. 이 상황의 아주 일반적인 예는 Exception 클래스를 확장하는 것입니다. 이제 ClassA 구현이 아래와 같이 변경되었다고 가정해 봅시다. 새로운 메서드 bar()가 추가되었습니다.
package com.journaldev.java.examples; public class ClassA { public void foo(){ } public int bar(){ return 0; } }
새로운 ClassA 구현을 사용하기 시작하면, ClassB에서
The return type is incompatible with ClassA.bar()
라는 컴파일 시간 오류가 발생합니다. 해결책은 슈퍼클래스 또는 서브클래스의 bar() 메서드를 변경하여 호환되게 만드는 것입니다. 상속 대신 구성을 사용했다면 이러한 문제가 발생하지 않았을 것입니다. 구성을 사용한 ClassB 구현의 간단한 예는 다음과 같을 수 있습니다.class ClassB{ ClassA classA = new ClassA(); public void bar(){ classA.foo(); classA.bar(); } }
-
상속에서는 접근 제어가 없지만, 구성에서는 접근을 제한할 수 있습니다. 우리는 슈퍼클래스 메소드를 서브클래스에 접근할 수 있는 다른 클래스에 노출시킵니다. 따라서 슈퍼클래스에 새로운 메소드가 도입되거나 슈퍼클래스에 보안 취약점이 있는 경우, 서브클래스는 취약해집니다. 구성에서는 사용할 메소드를 선택하기 때문에 상속보다 보안이 더욱 우수합니다. 예를 들어, ClassB에서 아래 코드를 사용하여 다른 클래스에 ClassA foo() 메소드를 노출시킬 수 있습니다.
class ClassB { ClassA classA = new ClassA(); public void foo(){ classA.foo(); } public void bar(){ } }
이것은 상속보다 구성의 주요 장점 중 하나입니다.
-
합성은 다중 서브클래스 시나리오에서 유용한 메서드 호출의 유연성을 제공합니다. 예를 들어 아래 상속 시나리오가 있다고 가정해 봅시다.
abstract class Abs { abstract void foo(); } public class ClassA extends Abs { public void foo(){ } } class ClassB extends Abs { public void foo(){ } } class Test { ClassA a = new ClassA(); ClassB b = new ClassB(); public void test(){ a.foo(); b.foo(); } }
그렇다면 더 많은 서브클래스가 있는 경우 합성은 각 서브클래스에 대해 하나의 인스턴스를 갖는 방식으로 코드를 더럽히게 만들까요? 아닙니다. Test 클래스를 다음과 같이 다시 작성할 수 있습니다.
class Test { Abs obj = null; Test1(Abs o){ this.obj = o; } public void foo(){ this.obj.foo(); } }
이렇게 하면 생성자에서 사용된 객체에 따라 어떤 서브클래스를 사용할지에 대한 유연성을 얻을 수 있습니다.
-
상속 대신 합성을 사용하는 또 다른 이점은 테스트 범위입니다. 합성에서 단위 테스트는 다른 클래스에서 사용하는 모든 메서드를 알기 때문에 쉽습니다. 테스트를 위해 그것을 모의할 수 있습니다. 반면에 상속에서는 수퍼클래스에 크게 의존하고 수퍼클래스의 모든 메서드가 사용될지 알지 못합니다. 따라서 수퍼클래스의 모든 메서드를 테스트해야 합니다. 이것은 추가 작업이며 상속 때문에 불필요하게 수행해야 합니다.
상속 대 합성에 대한 설명은 여기까지입니다. 합성을 선택해야 하는 충분한 이유가 있습니다. 수퍼클래스가 변경되지 않을 것을 확신할 때만 상속을 사용하십시오.
Source:
https://www.digitalocean.com/community/tutorials/composition-vs-inheritance