12장 traits - codeport/scala GitHub Wiki

12.0

zeide

  • 코드 재활용
  • 클래스는 여러 trait를 믹스인 할 수 있다.

12.1 How traits work

kingori

  • 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

nephilim

  • trait를 사용할 때는 extends TraitA with TraitB with TraitC로 알고 있는데, 아래 인용구의 의미는?

    extends(superclass가 없는 경우), with(superclass를 가진 경우)

12.2 Thin versus rich interfaces

kingori

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)

nephilim

  • 정리는 kingori(짱)님이 잘해주심

  • thin interface - implement 시점에는 부담이 될 수 있음.

    > 개인적으로 method 개수가 아니라, **인터페이스에 포함될 수 있는 재사용 가능한 정보의 종류와 양**의 문제로 보여짐.
    > thin/rich로 나누어 접근한 다음 구현이 포함될 수 있는 trait로 rich interface에게 기울였다는 말은 반만 맞다고 봄.
    > 책에도 나와 있는 간단한 예로, Ordered와 같은 thin interface에서도 불필요한 반복 구현을 막아줄 수 있다. 
    > 기능이 분화할 수록 rich > thin interface로의 전개를 보이는 경향도 있다.
    

12.3 Example: Rectangular objects

###kingori

skip

12.4 The Ordered trait

###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 } } }

12.5 Traits as stackable modifications

kingori

  • 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

nephilim

  • trait은 클래스보다 세부적인 단위로 모듈화하여 재사용하는 것을 가능하게 함
  • stackable modifications - modification은 결국 오버라이딩(abstract override)에 의해 이루어짐
  • 앞에서 뒤로 적용된다고 외우면 편하기는 한데, 사실 kingori님의 주석처럼 a -> Multi -> Add -> ValueImpl의 구조에 virtual method invocation이 더해졌다는 걸 이해해야 함

12.6 Why not multiple inheritance?

kingori

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

nephilim

  • Linearization은 Tree를 그리고 살펴보면, 직관적으로 이해가 됩니다. 모든 가지를 먼 곳에서부터 페인트 칠한다고 생각해 보면...

12.7 To trait, or not to trait?

kingori

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!

nephilim

  • 어이쿠 머리야~

12.8 Conclusion

kingori

skip

⚠️ **GitHub.com Fallback** ⚠️