# 12장 다형성
조영호 저 "오브젝트: 코드로 이해하는 객체지향 설계, 2019"를 읽고 정리한 내용입니다.
상속목적: 다형성을 위한 서브 타입 계층을 구축하는 것
1 다형성
유니버셜 다형성
Parametric. 예, List<T> 제너릭
Inclustion (=서브타이핑). 예, discounPoliciy.~()
임시(Ad hoc) 다형성
Overloading. 예, plus(int), plus(String), ...
Coercion. 예, 1+1 1+'s'
이번장에서는 Inclusion 다형성을 주로 다룬다. 따라서 주로, 상속 계층안에서 적절한 메서드 선택하는 방법을 이해할 것이다.
2 상속의 양면성
상속을 데이터관점이나 행동관점으로 보면 X (데이터공유, 메서드공유). 코드 재사용 관점이기 때문이다.
중요개념 - 업캐스팅 / 동적메서드탐색 / 동적바인딩 / sefl 참조 / super 참조
상속을 사용한 수강생 성적 계산을 예시로 든다.
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(0);
}
private double gradeAverage(Grade grade) {
return getScores().stream()
.filter(grade::include)
.mapToInt(Integer::intValue)
.average()
.orElse(0);
}
}
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;
}
}
//1차 실행
Lecture lecture = new Lecture("과목명", 70, Arrays.asList(81,95,75,50,45));
lecture.evaluate() // Pass:3 Fail:2
//2차 실행 - 업캐스팅+동적바인딩
Lecture lecture = new GradeLecture("과목명", 70, Arrays.asList(new Grade("A",100,95),new Grade("B",94,80),new Grade("C",79,70)), Arrays.asList(81,95,75,50,45));
lecture.evaluate() //질문 - 여기서 메서드 오버라이딩은 무엇이고, 메서드 오버로딩을 무엇일까?
오버라이딩 - evaluate, 오버로딩- average
3 업캐스팅과 동적바인딩
업캐스팅: 컴파일러는 명시적인 자식클래스가 부모클래스로의 대체를 허용한다. 다운캐스팅은 명시해야 한다. 동적바인딩: 수신하는 객체의 타입에 따라 실행되는 메서드가 변경되게 해준다. GradeLecture의 메서드 실행.
이전에 본 Professor가 예제가 의존성 역전 원칙을 따른다고 생각하겠지만 그렇게 말하기는 어렵다. 이유는 무엇이죠? 질문하기@@
객체지향에서 함수호출 보다 메시지 전송이란 말을 써야하는데 동적바인딩으로 어떤 특정 클래스의 메서드(함수)가 호출되는게 아니라 그 메서드를 찾기 위해 부모를 계속 탐색할 수 있기에 메시지 전송이 적절하다.
결론, 업캐스팅과 동적바인딩을 사용하면 부모클래스 참조에 대한 메시지 전송을 자식 클래스에 대한 메서드 호출로 변환할 수 있다. 그렇다면 객체지향 언어는 어떤 규칙에 따라 메서드 전송과 메서드 호출을 바인딩 하는 것일까?
4 동적 메서드 탐색과 다형성
sefl 참조: 동적 메서드 탐색은 self가 가리키는 객체의 클래스에서 시작해서 상속계층 역방향으로 이뤄지며, 메서드 탐색이 종료될때 self 참조는 자동 소멸된다.
자바의 self는 this
원리는 자동적인 메시지 위임 (부모까지탐색) + 동적인 문맥 (런타임) 으로 이뤄진다.
동적인 문맥
self 참조가 가리키는 객체 = 현재 객체

동적타입언어는 이해할 수 없는메시지를 처리할 능력을 가짐으로써 (method_missing, doesNotUnderstand) 메시지가 선언된 인터페이스 메서드가 정의된 구현을 분리할 수 있다.
이해할 수 없는 메시지와 도메인 특화 언어 DSL
동적타입언어의 위와 같은 특징은 메타 프로그래밍 영역에서 진가를 발휘하는데, 더 쉽고 강력한 DSL 도메인 특화언어를 개발 할수 있는 것으로 간주된다. 마틴 파울러는 이런 특징을 이용해 DSL을 개발하는 방식을 dynamic reception 동적 리셉션 이라고 부른다.
self 대 super
super 전송은 항상 메시지를 전송하는 클래스의 부모 클래스에서 부터 탐색이 시작된다.
메서드 호출 x 이유는.. 부모에 없고 더 상위로 올라갈 수도 있기에 메시지 전송 o
self는 메서드 탐색을 시작할 클래스를 반드시 실행시점에 동적으로 결정하지만 super 전송의 경우 컴파일 시점에 미리 정해 놓을 수 있다. 이유는 자식 클래스는 실행시점에도 얼마든 추가 될 수 있지만 부모는 정해져있으니..
위 강의 클래스의 getEvaluationMethod가 self의 예이다.
요약
지금 까지 살펴본 것처럼 동적바인딩, self참조, super 참조는 상속을 이용해 다형성을 구현하는 코드를 재사용하기 위한 가장 핵심적인 재료다. 동적 바인딩과 self 참조는 동일한 메시지를 수신하더라도 객체의 타입에 따라 적합한 메서드를 동적으로 선택할수 있게 한다. super 참조는 부모클래스의 코드에 접근할 수 있게 함으로써 중복코드를 제거할 수 있게 한다.
5 상속대 위임
추후 작성
Last updated