-
Notifications
You must be signed in to change notification settings - Fork 36
12장 traits
JeongHoon Byun (aka Outsider) edited this page Jul 21, 2013
·
15 revisions
- 코드 재활용
- 클래스는 여러 trait를 믹스인 할 수 있다.
- trait 선언엔 keyword
trait
사용. - class가 trait를 mix-in할 땐
extends
(superclass가 없는 경우),with
(superclass를 가진 경우)- 여러 trait를 mix-in할 경우엔 extends A with B with C ...
class A {}
class B extends A with T1 with T2 { }
- trait도 type이다.
scala> trait TTT { }
defined trait TTT
scala> class CCC extends TTT { }
defined class CCC
scala> val a:TTT = new CCC()
a: TTT = CCC@17e5fde
- trait의 method를 override할 수 있다
trait A { def a() = 3 }
class B extends A { override def a() = 5 }
- java의 interface와달리 field, state를 가질 수 있다.
- class parameter는 가질 수 없음 : java로 따지면 인자 없는 생성자만 가능함
- class의 super는 statically bound, trait의 super는 dynamically bound
-
trait를 사용할 때는 extends TraitA with TraitB with TraitC로 알고 있는데, 아래 인용구의 의미는?
extends
(superclass가 없는 경우),with
(superclass를 가진 경우)
thin interface(method수가 적은 interface) vs. rich interface(method수가 많은 interface)
- rich i/f는 다수의 method를 제공하므로 사용하는 측이 편함
- thin i/f는 구현할 메서드 수가 적어 구현이 편함
==> java는 thin i/f를 선호하는 경향이 있음. (많은 method를 구현하기 힘드니깐) 반면 scala의 trait를 이용하면 rich i/f 사용하기도 용이함.
- 직접 구현해야 하는 abstract method와(thin i/f)와 함께 이 메서드들을 사용하는 다양한 method를 제공 (rich i/f)
-
정리는 kingori(짱)님이 잘해주심
-
thin interface - implement 시점에는 부담이 될 수 있음.
> 개인적으로 method 개수가 아니라, **인터페이스에 포함될 수 있는 재사용 가능한 정보의 종류와 양**의 문제로 보여짐. > thin/rich로 나누어 접근한 다음 구현이 포함될 수 있는 trait로 rich interface에게 기울였다는 말은 반만 맞다고 봄. > 책에도 나와 있는 간단한 예로, Ordered와 같은 thin interface에서도 불필요한 반복 구현을 막아줄 수 있다. > 기능이 분화할 수록 rich > thin interface로의 전개를 보이는 경향도 있다.
###kingori
skip
###kingori
trait Ordered
: >, <, <=, >= 를 제공하는 trait. compare() 를 구현해야 함.
- 주의: equals 는 알아서 구현해야!
class A(val n: Int) extends Ordered[A] {
def compare(that: A) = this.n - that.n;
override def equals(that: Any):Boolean = {
that match {
case thatA: A => this.n == thatA.n;
case _ => false
}
}
}
- trait의 주요 용도
- turning a thin interface into a rich one
- providing stackable modifications to classes
- stackable modification - method를 abstract override로 선언
trait Doubling extends IntQueue { // 이 trait를 extends하려는 class는 IntQueue를 extends해야 함
abstract override def put(x: Int) { super.put(2 * x ) } // 남들이 먼저 구현을 제공하면 그 다음에 (마지막에) 호출됨
}
- mixin 선언 순서는 중요! : 순서는 다음 절에 나오지만 대충 앞에 나오는 mixin 부터 적용됨
abstract class Value { def value():Int }
class ValueImpl(vall:Int) extends Value { override def value():Int = { println("valueimpl");vall } }
trait Add extends Value { abstract override def value():Int = { println("add");super.value() + 3} }
trait Mult extends Value { abstract override def value():Int = { println("mult");super.value() *2} }
scala> val a = new ValueImpl(3) with Add with Mult
a: ValueImpl with Add with Mult = $anon$1@e904c4
scala> a.value // ( 3 + 3 ) * 2 : a -> Mult -> Add -> ValueImpl
mult
add
valueimpl
res90: Int = 12
scala> val b = new ValueImpl(3) with Mult with Add
b: ValueImpl with Mult with Add = $anon$1@110de08
scala> b.value // (3 * 2) + 3 : b -> Add -> Mult -> ValueImpl
add
mult
valueimpl
res91: Int = 9
- trait은 클래스보다 세부적인 단위로 모듈화하여 재사용하는 것을 가능하게 함
- stackable modifications - modification은 결국 오버라이딩(abstract override)에 의해 이루어짐
- 앞에서 뒤로 적용된다고 외우면 편하기는 한데, 사실 kingori님의 주석처럼 a -> Multi -> Add -> ValueImpl의 구조에 virtual method invocation이 더해졌다는 걸 이해해야 함
trait는 다중상속과 유사하지만 차이점도 있음
-
super : 다중상속에서 super.method() 의 method()는 대상이 바로 결정이 되지만, trait에선 class와 trait들의 linearization을 거쳐 대상이 결정됨. 따라서 stackable modification이 가능함.
- 다중상속에서 super를 만나면 어떤 method를 호출해야 하는 지 애매한 문제를 linealization으로 해결
- linearization: classs, 상속 class, trait들을 줄 세운 다음, super 호출이 이뤄지면 메서드들을 줄줄이 쌓아둠.
- linearization 순서 자체는 복잡함. 그냥 대충 먼저 언급된 type부터 쌓인다고 알아둬도 큰 무리 없음.
- class는 superclass, trait보다 먼저 나옴
abstract class Animal { def cry():Unit }
trait Furry extends Animal { abstract override def cry() { println("furry");super.cry()}}
trait HasLegs extends Animal { abstract override def cry() { println("hasLegs");super.cry()} }
trait FourLegged extends HasLegs { abstract override def cry() { println("fourLegged");super.cry()} }
class Doggy extends Animal { override def cry() { println("wuff!");}}
class DetailDoggy extends Doggy with Furry with FourLegged { override def cry() { println("detail");super.cry()}}
scala> new DetailDoggy ().cry() // DetailDoggy -> FourLegged -> HasLegs -> Furry -> Doggy
detail
fourLegged
hasLegs
furry
wuff!
-
abstract override 쌓는 순서 고민을 잘 해야 할 듯. 그냥 앞 결과를 뒤로 보낸다고 생각하면 될 듯? A -> B -> C 라면
- 실행 순서는 A-> B-> C . 실행되면서 stack에 쌓이고 super를 호출하면 다음 녀석이 호출. super를 호출 안하면?
abstract class A { def a():Unit }
trait B extends A { abstract override def a():Unit = { println("a") } }
class C extends A { override def a():Unit = { println("c") } }
class D extends C with B
scala> new D().a // D-> B -> C -> A. B에서 a 찍고 super 호출 안하니 그냥 끝내버림. **조심하자**
a
- Linearization은 Tree를 그리고 살펴보면, 직관적으로 이해가 됩니다. 모든 가지를 먼 곳에서부터 페인트 칠한다고 생각해 보면...
trait와 abstract class, concrete class 를 선택하는 가이드.
- concrete class: 행위를 재사용하지 않을 경우, 성능이 중요한 경우
- trait: 서로 관련성 없는 class 사이에서 재사용되어야 하는 경우
- abstract class
- java code에서 재사용해야 할 경우. 참고: abstract member만 가진 trait는 java의 interface로 생성됨
- compile된 형태로 배포할 경우. trait가 수정될 경우엔 이 trait를 상속하는 모든 class가 다시 compile되어야 하므로. 근데 이게 무슨 소리지? If outside clients will only call into the behavior, instead of inheriting from it, then using a trait is fine.
딱히 위 경우로 판단하기 어렵다면 trait!
- 어이쿠 머리야~
skip