2023. 2. 20. 21:33ㆍ객체지향
현재, 조영호님의 '오브젝트'라는 책을 읽으며 공부를 하고 있습니다.
책을 읽으면서 상속과 합성이라는 주제에대해 정리해보고 싶어 글을 작성하였습니다!
상속과 합성에 대해서 이야기하기 전에 의존성에 대해서 간단히 정리해보겠습니다.
의존성이란?
객체간의 의존성이란 곧 객체의 협력을 의미하는 데, 코드 상에서 파라미터나 리턴값 또는 지역변수 등으로 다른 객체를 참조하는 것을 말한다.
A 객체가 B 객체를 참조하고 있다고 하자.
만약, B 객체에 변경이 일어난다면 A 객체에도 영향을 미칠 수 있다.
우리는 이러한 영향을 ‘의존성 전이’라고 한다.
의존성 전의를 최소화하기 위해서는 컴파일 의존성이 아닌 런타임 의존성을 이용해야한다.
그렇다면, 컴파일 의존성은 무엇이고 런타임 의존성은 무엇일까?
컴파일 시간 의존성과 실행 시간 의존성
컴파일 의존성이란 코드를 컴파일 하는 시점에 결정되는 의존성이며, 일반적으로 추상 클래스나 인터페이스에 의존하지 않고, 구체 클래스에 의존할 때 컴파일 의존성을 갖게된다.(클래스 사이의 의존성) ⇒ 결합도 높음!
런타임 의존성이란 코드를 실행하는 시점에서 결정되는 의존성이며, 일반적으로 추상 클래스나 인터페이스에 의존할 때 런타임 의존성을 갖게된다. (객체 사이의 의존성) ⇒ 결합도 낮음!
public class Character {
Integer experienceAmount;
ExperiencePolicy experiencePolicy;
public void plusExperience(){
this.experienceAmount += experiencePolicy.calculateExperience();
}
}
public abstract class ExperiencePolicy {
public Integer calculateExperience(int increasingExperience){
int bonusExperience = calculateBonusExperience(increasingExperience);
return increasingExperience + bonusExperience;
}
public abstract Integer calculateBonusExperience(int increasingExperience);
}
public class FixedBonusPolicy extends ExperiencePolicy{
@Override
public Integer calculateBonusExperience(int increasingExperience) {
return 10;
}
}
public class PercentBonusPolicy extends ExperiencePolicy{
@Override
public Integer calculateBonusExperience(int increasingExperience) {
long round = Math.round(increasingExperience * 0.1);
return (int)round;
}
}
위의 코드에서는 Character 클래스와 ExperiencePolicy 클래스가 코드 의존성이 있다.
하지만, 실행 시점에 Charater의 인스턴스가 FixedBonusPolicy 객체 또는 PercentBonusPolicy 객체와 런타임 의존성이 생긴다.
여기까지 의존성에 대해 간단히 알아봤습니다.
상속과 합성에 대해 공부하기 전에...
왜 상속과 합성을 공부해야 하는 가??
- 상속과 합성의 차이와 언제 사용하는 지를 알아야 객체지향적 사고와 코드를 얻을 수 있기때문!!
상속
상속을 사용하면 기존 클래스가 가지고 있는 모든 속성과 행동을 새로운 클래스도 가지고 있게 됩니다.
따라서, 기존 클래스와 새로운 클래스간의 코드의 중복을 제거할 수 있고, 동일한 코드를 공유할 수 있게 됩니다.
그렇다면, 상속을 사용하는 이유는 메서드나 인스턴스 변수를 재사용하는 것일까요??
아닙니다!
상속을 사용하는 목적은 부모의 인터페이스를 자식의 인터페이스에 포함시켜 부모 클래스가 수신받을 수 있는 메시지를 동일하게 수신받는 것이다!
(아래는 이번 프로젝트의 코드 일부를 변형한 것이다.)
public class Character {
Integer experienceAmount;
ExperiencePolicy experiencePolicy;
public void plusExperience(){
this.experienceAmount += experiencePolicy.calculateExperience();
}
}
public abstract class ExperiencePolicy {
public Integer calculateExperience(int increasingExperience){
int bonusExperience = calculateBonusExperience(increasingExperience);
return increasingExperience + bonusExperience;
}
public abstract Integer calculateBonusExperience(int increasingExperience);
}
public class FixedBonusPolicy extends ExperiencePolicy{
@Override
public Integer calculateBonusExperience(int increasingExperience) {
return 10;
}
}
public class PercentBonusPolicy extends ExperiencePolicy{
@Override
public Integer calculateBonusExperience(int increasingExperience) {
long round = Math.round(increasingExperience * 0.1);
return (int)round;
}
}
위의 예시를 보면, ExperiencePolicy의 추상 메서드인 calculateBonusExperience() 메서드를 FixedBonusPolicy와 PercentBonusPolicy가 구현하고 있습니다.
이는 자식 클래스들의 인터페페이스는 부모 클래스의 인터페이스를 물려받았으므로 동일한 인터페이스들이 존재한다는 것이고, 부모 클래스가 메시지를 수신했을 때 자식 클래스들에따라서 다르게 응답할 수 있다는 것입니다.
즉, ExperiencePolicy에게 오는 calculateBonusExperience()메시지는 FixedBonusPolicy가 수신받을 수도 있고, PercentBonusPolicy가 수신받을 수도 있습니다.
이를, 다형성을 이용하기 위해 부모 클래스의 인터페이스를 자식 클래스가 상속받는 것!!
정리하자면, 상속을 사용할 때는 부모 클래스의 인터페이스에 따라 동일한 메시지를 다르게 응답하고 싶을 때 사용한다!!
그렇다면, 코드를 재사용 하고 싶을 때는 어떻게 해야하는 가???
코드를 재사용할 때는 합성을 사용합니다!
합성
합성이란 다른 객체의 인스턴스를 자신의 인스턴스 변수로 포함해서 재사용하는 방법을 말합니다.
public class Score{
private Sleep sleep;
...
public Integer calculateScore(){
sleep.evaluateSleep();
...
return score;
}
}
Score 클래스는 sleep 객체를 인스턴스 변수로 가지고 있습니다. -> 합성
현재, sleep 객체의 evaluateSleep()메서드를 재사용하고 있습니다!
그렇다면, 상속은 왜 안되는 것일까?
상속이 설계에 안 좋은 영향을 미치는 이유는 아래와 같다.
- 상속이 캡슐화를 위반한다.
- 설계를 유연하지 못 하게 만든다.
상속을 하기 위해서는 부모 클래스의 내부 구조를 잘 알고 있어야 한다. 이는 부모 클래스의 구현을 자식 클래스가 알고 있다는 사실이고, 곧 캡슐화가 약해진다는 의미이다. 캡슐화가 약해짐으로 인해 부모 클래스와 자식 클래스 간의 결합이 강하게 묶여 부모 클래스를 변경하기 어려워진다
상속을 하게 되면, 두 클래스의 관계가 컴파일 시점에서 결정나기 때문에 실행 시점에 객체의 종류를 변경하는 것이 불가능하다.
이에 반해, 합성은 의존관계에 있는 객체의 내부 코드를 알 필요가 없다. 그냥 인터페이스만 알고 있으면 된다.
따라서, 코드를 재사용을 위해서는 상속보다는 합성을 선호하는 것이 더 좋은 방법이다.
결론
코드를 재사용하는 경우에는 상속보다 합성을 선호하는 것이 옳지만 다형성을 위해 인터페이스를 재사용하는 경우에는 상속과 합성을 조합하는 것을 권장한다!
'객체지향' 카테고리의 다른 글
일급 컬렉션이 뭘까?? (0) | 2023.02.28 |
---|---|
추상화 - 인터페이스, 추상 클래스 (0) | 2023.02.20 |