-
Notifications
You must be signed in to change notification settings - Fork 7
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로 관찰됨)
- 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의 본문이 가장 나중에 평가된다.