-
Notifications
You must be signed in to change notification settings - Fork 36
12장 traits
kingori edited this page Oct 16, 2011
·
15 revisions
- trait 선언엔 keyword
trait
사용. - class가 trait를 mix-in할 땐
extends
(superclass가 없는 경우),with
(superclass를 가진 경우)- 여러 trait를 mix-in할 경우엔 with 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
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
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는 다중상속과 유사하지만 차이점도 있음
-
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를 class하면 다음 녀석이 호출. 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
TBD