목차
- ☆ 표시가 붙은 부분은 스터디 중 나온 얘기 혹은 제 개인적인 생각이나 제가 이해한 방식을 적어놓은 것으로, 책에 나오지 않는 내용입니다. 따라서 책에서 말하고자 하는 바와 다를 수 있습니다.
- 모든 이미지의 출처는 오브젝트(조용호 저) 책 입니다.
CHAPTER 12. 다형성
⚈ 코드 재사용을 목적으로 상속을 사용하면 변경하기 어렵고 유연하지 못한 설계에 이를 확률이 높아진다. 상속의 목적은 코드 재사용이 아니다. 상속은 타입 계층을 구조화하기 위해 사용해야 한다.
⚈ 12장 : 상속의 일차적인 목적이 코드 재사용이 아니라 서브타입의 구현이라는 사실을 이해하기 위한 챕터
01 다형성
⚈ 다형성(Polymorphism) : 하나의 추상 인터페이스에 대해 코드를 작성하고 이 추상 인터페이스에 대해 서로 다른 구현을 연결할 수 있는 능력. 즉, 여러 타입을 대상으로 동작할 수 있는 코드를 작성할 수 있는 방법
⚈ 다형성의 분류
- 매개변수 다형성 : 클래스의 인스턴스 변수나 메서드의 배개변수 타입을 임의의 타입으로 선언한 후 사용하는 시점에 구체적인 타입으로 지정하는 방식. 예를들어 자바에서 제너릭 타입으로 List에서 임의의 타입 T로 보관할 요소를 지정하고 있다. 실제 인스턴스를 생성하는 시점에 T를 구체적인 타입으로 지정할 수 있다.
- 포함 다형성 : 특별한 언급 없이 다형성이라고 할 때는 포함 다형성을 의미한다. 메시지가 동일하더라도 수신할 객체의 타입에 따라 실제로 수행되는 행동이 달라지는 능력 (=서브타입(subtype) 다형성)
- 오버로딩 다형성 : 클래스 안에 동일한 이름의 메서드가 존재하는 경우 (메서드 오버로딩)
- 강제 다형성 : 언어가 지원하는 자동적인 타입 변환이나 사용자가 직접 구현한 타입 변환을 이용해 동일한 연산자를 다양한 타입에 상용할 수 있는 방식. 예를들어 "ABC" + 3 = "ABC3"
02 상속의 양면성
⚈ 데이터 관점의 상속 : 상속을 이용하면 부모 클래스에서 정의한 모든 데이터를 자식 클래스의 인스턴스에 자동으로 포함시킬 수 있다.
⚈ 행동 관점의 상속 : 부모 클래스에서 정의한 일부 메서드 역시 자동으로 자식 클래스에 포함시킬 수 있다.
⚈ 타입 계층에 대한 고민 없이 코드를 재사용하기 위해 상속을 사용하면 이해하기 어렵고 유지보수하기 버거운 코드가 만들어질 확률이 높다..
⚈ 상속을 이용한 강의 평가
- 코드는 아래와 같다.
public class Lecture {
private int pass;
private String title;
private List<Integer> scores = new ArrayList<>();
public Lecture(String title, int pass, List<Integer> scores) {
this.title = title;
this.pass = pass;
this.scores = scores;
}
public double average() {
return scores.stream().mapToInt(Integer::intValue).average().orElse(0);
}
public List<Integer> getScores() {
return Collections.unmodifiableList(scores);
}
public String evaluate() {
return String.format("Pass:%d Fail:%d", passCount(), failCount());
}
private long passCount() {
return scores.stream().filter(score -> score >= pass).count();
}
private long failCount() {
return scores.size() - passCount();
}
}
----
public class Grade {
private String name;
private int upper,lower;
private Grade(String name, int upper, int lower) {
this.name = name;
this.upper = upper;
this.lower = lower;
}
public String getName() {
return name;
}
public boolean isName(String name) {
return this.name.equals(name);
}
public boolean include(int score) {
return score >= lower && score <= upper;
}
}
----
public class GradeLecture extends Lecture {
private List<Grade> grades;
public GradeLecture(String name, int pass, List<Grade> grades, List<Integer> scores) {
super(name, pass, scores);
this.grades = grades;
}
@Override
public String evaluate() {
return super.evaluate() + ", " + gradesStatistics();
}
private String gradesStatistics() {
return grades.stream().map(grade -> format(grade)).collect(joining(" "));
}
private String format(Grade grade) {
return String.format("%s:%d", grade.getName(), gradeCount(grade));
}
private long gradeCount(Grade grade) {
return getScores().stream().filter(grade::include).count();
}
public double average(String gradeName) {
return grades.stream()
.filter(each -> each.isName(gradeName))
.findFirst()
.map(this::gradeAverage)
.orElse(0d);
}
private double gradeAverage(Grade grade) {
return getScores().stream()
.filter(grade::include)
.mapToInt(Integer::intValue)
.average()
.orElse(0);
}
}
- 데이터 관점의 상속 : 자식 클래스의 인스턴스 안에 부모 클래스의 인스턴스를 포함하는 것으로 볼 수 있다. 따라서 자식 클래스의 인스턴스는 자동으로 부모 클래스에서 정의한 모든 인스턴스 변수를 내부에 포함하게 된다.
- 행동 관점의 상속 : 부모 클래스의 모든 퍼블릭 메서드는 자식 클래스의 퍼블릭 인터페이스에 포함된다. 따라서 외부의 의 객체가 부모 클래스의 인스턴스에게 전송할 수 있는 모든 메시지는 자식 클래스의 인스턴스에게도 전송할 수 있다. 이 때 런타임에 시스템이 자식 클래스에 정의되지 않은 메서드가 있을 경우 이 메서드를 부모 클래스 안에서 탐색한다. 자바에서는 모든 클래스의 부모 클래스인 Object까지 상속 계층을 탐색한다.
03 업캐스팅과 동적 바인딩
⚈ 코드 안에서 선언된 참조 타입과 무관하게 실제로 메시지를 수신하는 객체의 타입에 따라 실행되는 메서드가 달라질 수 있는 것은 업캐스팅과 동적 바인딩이라는 메커니즘이 작용하기 때문이다.
- 업캐스팅 : 부모 클래스 타입으로 선언된 변수에 자식 클래스의 인스턴스를 할당하는 것이 가능
- 동적 바인딩 : 선언된 변수의 타입이 아니라 메시지를 수신하는 객체의 타입에 따라 실행되는 메서드가 결정
⚈ 업캐스팅, 다운캐스팅
⚈ 동적 바인딩
- 전통적인 언어들은 호출될 함수를 컴파일타임에 결정한다. -> 정적 바인딩(static binding), 초기 바인딩(early biding), 또는 컴파일타임 바인딩(compile-time binding)
- 실행될 메서드를 런타임에 결정하는 방식 -> 동적 바인딩(dynamic binding) 또는 지연 바인딩(late binding)
04 동적 메서드 탐색과 다형성
⚈ 객체지향 시스템은 다음 규칙에 따라 실행할 메서드를 선택한다.
- 메시지를 수신한 객체는 먼저 자신을 생성한 클래스에 적합한 메서드가 존재하는지 검사한다. 존재하면 메서드를 실행하고 탐색을 종료한다.
- 메서드를 찾지 못했다면 부모 클래스에서 메서드 탐색을 계속한다. 이 과정은 적합한 메서드를 찾을 때까지 상속 계층을 따라 올라가며 계속된다.
- 상속 계층의 가장 최상위 클래스에 이르렀지만 메서드를 발견하지 못한 경우 예외를 발생시키며 탐색을 중단한다.
⚈ self 참조(self reference) : 객체가 메시지를 수신하면 컴파일러는 self 참조라는 임시 변수를 자동으로 생성한 후 메시지를 수신한 객체를 가리키도록 설정한다. (자바에서는 self 참조를 this라고 부른다.)
⚈ 동적 메서드 탐색의 입장에서 상속 계층은 메시지를 수신한 객체가 자신이 이해할 수 없는 메시지를 부모 클래스에게 전달하기 위한 물리적인 경로를 정의한 것으로 볼 수 있다. 적절한 메서드를 찾을 때까지 상속 계층을 따라 부모 클래스로 처리가 위임된다.
⚈ ☆ 정적 타입 언어 : 컴파일 시에 변수의 타입이 결정되는 언어 - 자바, C, C++, C# 등
⚈ ☆ 동적 타입 언어 : 런타임 시 자료형이 결정 - Python, js, Ruby 등
⚈ super 참조(super reference) : '지금 이 클래스의 부모 클래스에서부터 메서드 탐색을 시작하세요'
'Study > 오브젝트' 카테고리의 다른 글
[오브젝트] 14장. 일관성 있는 협력 (0) | 2023.01.21 |
---|---|
[오브젝트] 13장. 서브클래싱과 서브타이핑 (0) | 2023.01.14 |
[오브젝트] 11장. 합성과 유연한 설계 (0) | 2023.01.04 |
[오브젝트] 10장. 상속과 코드 재사용 (0) | 2023.01.03 |
[오브젝트] 9장. 유연한 설계 (0) | 2022.12.22 |
댓글