- 트레이트 : 상속을 통해 코드를 재사용할 수 있는 기본 코드 단위
- 트레이트를 상속하면 클래스에서 트레이트의 메소드와 필드 사용 가능
- 상속과 달리 여러 개 트레이트 혼합해 사용 가능
- 트레이트
=> 풍부한 인터페이스를 만들 수 있다.
=> 쌓을 수 있는 변경을 정의할 수 있다.
- 트레이트는 전체적인 개념보다는 간단한 개념의 조각을 담는다. => 설계를 구체적으로 진행해가면서 각 조각을 조합해 더 완전한 개념으로 확장
12.1 트레이트의 동작 원리
< 트레이트 정의 > trait Philosophical { |
- 클래스와 마찬가지로 AnyRef의 서브 클래스
- extends나 with 키워드를 사용해 클래스 조합 가능, 보통 상속보다는 믹스인이라고 한다.
< extends 키워드를 사용한 믹스인 > class Frog extends Philosophical { override def toString = "green" } |
: extends를 사용하면 트레이트의 슈퍼클래스를 암시적으로 상속
=> Frog 클래스는 AnyRef의 서브 클래스, Philosophical을 믹스인
val phil : Philosophical = frog phil.phillosophize() // I consume memory, therefore I am! |
: 트레이트 타입으로 믹스인한 어떤 객체 초기화 가능
< with 키워드를 사용한 믹스인 > class Animal trait HasLegs
class Frog extends Animal with Philosophical with HashLegs { |
: extends 키워드를 통해 슈퍼 클래스를 명시적으로 지정 가능, 여러 트레이트 믹스인 가능
- 트레이트는 클래스에서 할 수 있는 모든 것(override, 추상 멤버, 멤버 정의 ...)이 가능하지만, 두 가지는 안 됨
1. 트레이트는 클래스 파라미터를 가질 수 없다. ( Ex. trait NoPoint(x:Int, y:Int) // 컴파일 오류 )
2. 클래스는 super 호출을 정적으로 바인딩하지만 트레이트는 동적으로 바인딩한다.
( Ex. super.toString은 클래스에서 사용하면 어떤 메소드를 호출할지 알 수 있지만 트레이트는 그렇지 않다.)
12.2 간결한 인터페이스와 풍부한 인터페이스
풍부한 인터페이스 | 간결한 인터페이스 | |
구현하는 사람 | 구현할 메소드 많음 | 구현할 메소드 적음 |
사용하는 사람 | 편의 제공 | 별도 메소드 구현 |
- 자바 인터페이스는 간결한 경향
< 자바의 CharSequence 인터페이스 > trait CharSequence { |
: String 클래스의 여러 메서드를 CharSequence 인터페이스에 포함시켰다면 CharSequence 구현에 큰 부담
=> 스칼라는 트레이트에 구현을 넣을 수 있어 트레이트 내부에 한 번만 구현하면 됨
=> 풍부한 인터페이스 제공이 쉽다.
12.3 예제: 직사각형 객체
: 그래픽 라이브러리는 여러 직사각형 모양의 클래스(창, 이미지, 비트맵 ...) 포함 => 너비, 높이, 좌측/우측 위치 같은 속성을 가진 라이브러리를 만들고 싶다.
< 트레이트 X > class Point(val x: Int, val y: Int)
abstract class Component {
< 트레이트 O > trait Rectangular {
abstract class Component extends Rectangular {
class Rectangle(val topLeft: Point, val bottomRight: Point) extends Rectangular { |
: 풍부한 트레이트를 통해 코드 반복을 피할 수 있다.(자바는 트레이트처럼 구현하기 힘듬, 자바 8 이후부터 위 트레이트처럼 구현하려면 할 수 있다.)
12.4 Ordered 트레이트
< 6장에 나온 Rational 분수 클래스 > class Rational(n: Int, d: Int) { this.numer * that.denom < that.numer * this.denom |
: ">", "<=", ">=" 연산을 "<"로 표현 => 비교가 가능한 클래스라면 "<" 메소드로 다른 비교 연산을 표현하는 로직이 중복 => Ordered 트레이트 제공
< Ordered 트레이트 >
: compare 메소드 구현과 Ordered 트레이트를 믹스인하면 < , > , <= , >= 비교 메소드 제공
* compare 호출 대상 객체와 인자로 전달 받은 객체가 동일하면 0, 호출 대상 객체가 인자보다 작으면 음수, 크면 양수 반환
* 믹스인할 시 Ordered는 타입 파라미터(19장)를 명시한다.
class Rational(n: Int, d: Int) extends Ordered[Rational] { ( this.numer * that.denom ) - ( that.numer * this.denom ) } |
- Ordered 트레이트가 equals 메소드 구현은 해주지 않는다. 구현하려면 전달받을 객체의 타입을 알아야하는 데 타입 소거 때문에 알 수가 없다. => Ordered를 상속하더라도 equals는 직접 정의해야 한다.(이를 우회할 방법은 30장)
* 타입 소거 : 컴파일러가 타입을 검사하고 나서 바이트 코드를 만들면서 타입 관련 정보를 제거하는 것
* 트레이트는 추상 메서드를 가지고 공통된 메서드를 구현해 믹스인한 클래스에서 추상 메소드만 구현만 하도록 하고 나머지 메소드 재사용
12.5 트레이트를 이용해 변경 쌓아 올리기
: 트레이트 => 클래스의 메소드를 변경할 수 있을 뿐만 아니라 이 변경 위에 다른 변경을 계속 쌓을 수 있다.
< Ex. put과 get 메소드가 있는 정수로 된 큐 >
abstract class IntQueue {
import scala.collection.mutable.ArrayBuffer |
< 트레이트 정의 > trait Doubling extends IntQueue { // 큐에 있는 모든 정수를 두 배로 만든다. }
trait Incrementing extends IntQueue { // 큐에 있는 모든 정수에 1을 더한다. } |
: 트레이트의 extends 뒤에 명시된 클래스는 트레이트를 믹스인한 클래스의 슈퍼 클래스어야 한다.
=> Doubling 트레이트는 IntQueue를 상속한 클래스에만 믹스인할 수 있다.
: 위 트레이트는 전체 큐 클래스를 정의하기보다는 큐 클래스 put에 대한 동작을 정의 => put 메소드에 변경을 가한다.
: 위 3개의 트레이트를 겹쳐서 사용해 각 put 메소드에 정의된 동작을 모두 반영할 수 있다.
<= 트레이트 추상 메소드 내 super 호출이 동적으로 바인딩 되기 때문에(자세한 내용은 밑에)
: 트레이트에서 메소드 내에 super을 호출했다고 abstract override로 표시해야 한다.
: abstract override 메소드가 어떤 트레이트에 있다면 반드시 해당 메소드에 대한 구체적 구현을 제공하는 클래스에 믹스인해야 한다.
class MyQueue extends BasicIntQueue with Doubling // val queue = new BasicIntQueue with Doubling 과 같다. val queue = new MyQueue queue.put(10) queue.get() // 20 |
여러 트레이트를 적용할 때 믹스인 순서가 중요하다. 가장 오른쪽에 있는 트레이트의 메소드를 먼저 호출
val queue = new BasicIntQueue with Incrementing with Filtering queue.put(-1); queue.put(0); queue.put(1); queue.get() // 1 queue.get() // 2
val queue = new BasicIntQueue with Filteringwith Incrementing queue.put(-1); queue.put(0); queue.put(1); queue.get() // 0 queue.get() // 1 queue.get()// 2 |
12.6 왜 다중 상속은 안 되는가?
- 다중 상속 언어에서 super를 호출할 때 어떤 메소드를 부를지에 대한 결정은 호출이 이뤄지는 곳(컴파일 시점)에 이뤄진다.
- 스칼라는 특정 클래스에 믹스인한 클래스와 트레이트를 선형화해서 어떤 메소드를 호출할 지 결정한다.
< 다중 상속 문제 > val q = new BasicIntQueue with Incrementing with Doubling
trait MyQueue extends BasicIntQueue with Incrementing with Doubling { Incrementing.put(x) // (스칼라 코드 아님) Doubling.put(x) } // put 호출을 명시했을 때, x에 대해 put을 따로 수행 } |
=> 스칼라는 "선형화"를 통해 다중 상속 문제 해결
< 선형화 >
- 클래스를 new로 인스턴스화할 때 클래스 자신, 조상 클래스들, 믹스인한 트레이트를 한 줄로 세워 순서를 정한다.
- 순서 규칙 : 어떤 클래스는 자신의 슈퍼 클래스나 믹스인해 넣은 트레이트보다 앞에 위치, 오른쪽에 있는 슈퍼 클래스나 트레이트를 앞에 위치
- 선형화 순서를 구할 때 왼쪽에 선언된 슈퍼 클래스, 트레이트의 선형화를 구하는 것부터 시작해서 선형화 결과를 쌓는다.( 선형화 결과에는 각 클래스/트레이트가 중복 X )
- super을 클래스에서 호출할 경우 선형화로 정해진 순서 한 단계 다음에 있는 클래스/트레이트의 메소드가 호출
Ex. Cat 선형화 구하기
class Animal trait Furry extends Animal trait HasLegs extends Animal trait FourLegged extends HasLegs class Cat extends Animal with Furry with FourLegged |
1. 가장 왼쪽에 선언된 Animal 클래스 선형화 : Animal => AnyRef => Any
2. Furry 트레이트 선형화 : Furry => Animal => AnyRef => Any (이전 선형화에 쌓아올리고 중복된 클래스/트레이트 제거)
* Furry 선형화 자체 : Furry => Animal => AnyRef => Any
3. FourLegged 선형화 : FourLegged => HashLegs => Furry => Animal => AnyRef => Any (이전 선형화에 쌓아올리고 중복된 클래스/트레이트 제거)
* FourLegged 선형화 자체 : FourLegged => HashLegs => Animal => AnyRef => Any
4. Cat 선형화 : Cat => FourLegged => HashLegs => Furry => Animal => AnyRef => Any
==> 위에 있는 클래스나 트레이트 중 하나가 super을 이용해 메소드를 호출하면 선형화 순서상 자기보다 오른쪽에 있는 첫 번째 구현을 호출한다.
12.7 트레이트냐 아니냐, 이것이 문제로다
- 어떤 행위를 재사용하지 않을 거라면, 클래스로 만들어라
- 서로 관련이 없는 클래스에서 어떤 행위를 여러 번 재사용해야 한다면 트레이트로 작성하라
- 스칼라에서 정의한 내용을 자바 코드에서 상속해야 한다면 추상클래스를 사용하라
* 자바에는 트레이트와 유사한 개념이 없음
* 스칼라 클래스를 자바 클래스에 상속하는 것은 자바 클래스를 상속하는 것과 같다.
* 구현 코드 없는 추상 메소드만 있는 스칼라 트레이트는 자바 인터페이스와 같기 때문에 예외
- 컴파일한 바이너리 형태로 배포할 예정이면 추상 클래스 추천
* 특정 트레이트에 멤버를 추가하거나 제거하면 그 트레이트를 상속하는 모든 클래스는 재컴파일해야 함(트레이트를 상속하지 않고 이용만 한다면 문제 없음)
- 판단이 서지 않으면 트레이트를 사용
'스칼라' 카테고리의 다른 글
스칼라 14장 단언문과 테스트(Programming in Scala, 3rd) (0) | 2019.06.02 |
---|---|
스칼라 13장 패키지와 임포트(Programming in Scala, 3rd) (0) | 2019.06.01 |
스칼라 11장 스칼라의 계층 구조(Programming in Scala, 3rd) (0) | 2019.06.01 |
스칼라 10장 상속과 구성(Programming in Scala, 3rd) (0) | 2019.06.01 |
스칼라 9장 흐름 제어 추상화(Programming in Scala, 3rd) (0) | 2019.06.01 |