Skip to content

9.3 Stackable Traits

QyuStory edited this page Nov 7, 2016 · 6 revisions

트레이트 쌓기

  • 코드의 재사용성을 향상시키고, 트레이트를 한 번에 하나 이상 사용하기(즉, 트레이스를 쌓기) 위해 할 수 있는 추가 세분화가 있다.

(1) '클릭할 수 있는'은 abstract Widget에 추가 할 수 있지만, 모든 GUI 위젯을 누를 수 있는것은 아니기 때문에 새로운 trait인 Clickable을 만든다.

// src/main/scala/progscala2/traits/ui2/Clickable.scala
package progscala2.traits.ui2 // traits.ui에 있는 타입을 재구현하기 때문에 새로운 패키지를 선언한다.

trait Clickable {
  def click(): Unit = updateUI() // 공개 메서드인 click은 구체적 메서드이다. 여기서는 updateUI에 모든 것을 위임한다.

  protected def updateUI(): Unit // updateUI는 공개 보호 메서드이며 추상 메서드이다. 구현 클래스에서는 이 메서드를 override해야함.
}
  • 메서드 click과 updateUI은 세부적인 구현과 공개적인 추상화를 분리하기 위해 자주 쓰이는 형태이다.

다음은 traits를 리팩토링한 버튼이다.

// src/main/scala/progscala2/traits/ui2/Button.scala
package progscala2.traits.ui2
import progscala2.traits.ui.Widget

class Button(val label: String) extends Widget with Clickable {
  protected def updateUI(): Unit = { /* logic to change GUI appearance */ }
}
  • 우리가 관심있는 것은 버튼 눌림과 같은 이벤트를 관찰하는 것이기 때문에 위와같이 리팩토링하면 실제로 버튼을 관찰 안하려는 것을 알 수 있다.

다음은 Clickable을 관찰하는 것에만 집중한 traits다.

// src/main/scala/progscala2/traits/ui2/ObservableClicks.scala
package progscala2.traits.ui2
import progscala2.traits.observer._

trait ObservableClicks extends Clickable with Subject[Clickable] {
  abstract override def click(): Unit = { // abstract는 override keyword를 사용한다.
    super.click() // super는 Clickable인지 Subject인지 명확하지 않다 -> 그래서 abstract가 필요하다.
    notifyObservers(this)
  }
}
  • 위 super는 Button과 같이 click을 정의한 concrete 인스턴스와 traits이 합쳐졌을때 정의된다.
  • ObservableClicks.click이 body가 있음에도, abstract 키워드는 컴파일러에게 click이 완전히 구현되지 않았다고 전한다.

위 trait을 Button과 concrete method(click)을 함께 사용해보자

// src/main/scala/progscala2/traits/ui2/click-count-observer.sc
import progscala2.traits.ui2._
import progscala2.traits.observer._

// No override of "click" in Button required.
val button = new Button("Click Me!") with ObservableClicks

class ClickCountObserver extends Observer[Clickable] {
  var count = 0
  def receiveUpdate(state: Clickable): Unit = count += 1
}

val bco1 = new ClickCountObserver
val bco2 = new ClickCountObserver

button addObserver bco1
button addObserver bco2

(1 to 5) foreach (_ => button.click())

assert(bco1.count == 5, s"bco1.count ${bco1.count} != 5")
assert(bco2.count == 5, s"bco2.count ${bco2.count} != 5")
println("Success!")
  • Button을 선언할 수 있고, click을 override하지 않고 ObservableClicks을 합칠 수 있다.

  • Clickable을 합성함으로써 click을 지원하는 GUI 객체를 생성할 수 있다.

  • 이와같이 mix in trait은 미세하게 기능을 합성하는 것은, 꽤 강력하지만, 종종 사용이 지나칠 수 있다.

    • trait이 너무 많으면 컴파일 시간이 너무 오래걸린다. 컴파일러가 출력 바이트 코드를 합성하기 위해 더 많은 작업을 해야하기 때문이다
    • scaladoc을 볼 때 trait의 목록이 너무 길면 라이브러리 사용자가 지레 겁 먹을 수 있다(?)

(2) 두번째 trait을 추가함을써 예제를 끝냄

  • 자바빈스의 '거부할 수 있는(vetoable)' 개념과 비슷하게, 미리 지정한 횟수 이상 마우스 버튼이 눌리면 이벤트를 무시하는 trait을 만들어 구현해 보자.
// src/main/scala/progscala2/traits/ui2/VetoableClicks.scala
package progscala2.traits.ui2
import progscala2.traits.observer._

trait VetoableClicks extends Clickable { // Clickable의 확장
  // 허용하는 최대 눌림 횟수는 기본값
  val maxAllowed = 1 // click횟수의 최댓값
  private var count = 0
  abstract override def click() = {
    if (count < maxAllowed) { // 허용된 값 이상으로 눌리면 더이상 super로 click이 가지 않는다.
    count += 1
    super.click()
    }
  }
}
  • maxAllowed는 'val'로 선언되었지만, 이 trait을 class나 trait과 mix in하여 value를 override하여 변경할 수 있다.
  • 값을 2로 변경해보자
// src/main/scala/progscala2/traits/ui2/vetoable-click-count-observer.sc
import progscala2.traits.ui2._
import progscala2.traits.observer._

// No override of "click" in Button required.
val button =
    new Button("Click Me!") with ObservableClicks with VetoableClicks {
  override val maxAllowed = 2 // maxAllowed의 값을 2로 override 함
}

class ClickCountObserver extends Observer[Clickable] { // 전과 같은 ClickObserver 사용
  var count = 0
  def receiveUpdate(state: Clickable): Unit = count += 1
}

val bco1 = new ClickCountObserver
val bco2 = new ClickCountObserver

button addObserver bco1
button addObserver bco2

(1 to 5) foreach (_ => button.click())

assert(bco1.count == 2, s"bco1.count ${bco1.count} != 2") // 5번 click될 지라도 카운트 된 수는 2
assert(bco2.count == 2, s"bco2.count ${bco2.count} != 2")
println("Success!")
  • 아래와 같이 순서를 바꾼다면?
val button = new Button("Click Me!") with VetoableClicks with ObservableClicks
  • assertion fail 발생(카운트 된 수가 5로 관찰됨)
  1. VetoableClicks과 ObservableClicks을 mix in할 때, 어떤 version의 click이 먼저 실행될까?
  • 오른쪽에서 왼쪽으로, 선언 순서대로 실행됨
// new Button("Click Me!") with VetoableClicks with ObservableClicks
def ObservableClicks.click() = {
  if (count < maxAllowed) { // super.click => VetoableClicks.click
    count += 1
    {
      updateUI() // super.click => Clickable.click
    }
  }
  notifyObservers(this)
}

// new Button("Click Me!") with ObservableClicks with VetoableClicks
def VetoableClicks.click() = {
  if (count < maxAllowed) {
    count += 1
    { // super.click => ObservableClicks.click
      {
        updateUI() // super.click => Clickable.click
      }
      notifyObservers(this)
    }
  }
}
  • 선형화(Linearization)라는 알고리즘을 사용해서 구체적인 상속 계층에 trait이나 class의 우선순위를 해결한다.
  • trait의 우선순위는 오른쪽에서 왼쪽 순서며, maxAllowed를 override한 것과 같은 button의 본문이 가장 나중에 평가된다.
Clone this wiki locally