409
9
장
트레이트
●
두가지예외를제외하면구체적클래스를결코상속하지않는다. (
2
)자동화된단위테스트를지원하기위한
테스트용버전을만드는경우
적용 가능한 상황이 아니다.
●
상속이바람직한접근방법이라생각되는경우,상속대신동작을트레이트로분할해서각각의트레이트를혼
합할방법이없는지살펴보라.
방금 그렇게 했다!!
●
논리적상태를부모
-
자식타입경계에걸쳐서나눠담지말라.
버튼이나 다른
UI
위젯의 논리적이고 눈에 보이는 상태는 내부의 상태 변경 방식과 서로
겹치지 않는다. 따라서
UI
상태는 여전히
Button
에 들어 있으며, 관찰자 패턴과 관련 있는
상태는
State
트레이트 안에 들어 있다.
9.3
트레이트 쌓기
코드의 재사용성을 향상시키고, 트레이트를 한 번에 하나 이상 사용하기 (즉, 트레이트를 ‘쌓
기’ ) 위해 할 수 있는 추가 세분화가 있다.
먼저 ‘누를 수 있음
clickablility
’은
GUI
에서 버튼에만 국한된 것이 아니다. 따라서 이런 논리도 분리
해야 할 것이다. 이를
Button
의 부모지만, 아직까지는 비어 있는
Widget
에 넣을 수도 있지만,
모든
GUI
위젯을 누를 수 있는 것은 아니다. 대신에
Clickable
이라는 트레이트를 도입하자.
//
src
/
main
/
scala
/
progscala2
/
traits
/
ui2
/
Clickable
.
scala
package progscala2
.
traits
.
ui2
//
➊
trait Clickable
{
def click
():
Unit
=
updateUI
()
//
➋
protected def updateUI
():
Unit
//
➌
}
➊
traits
.
ui
에 있는 타입을 재구현하고 있기 때문에 새로운 패키지를 선언한다.
410
2
부
기본기 다지기
➋ 공개 메서드인
click
은 구체적 메서드다. 여기서는
updateUI
에 모든 것을 위임한다.
➌
updateUI
메서드는 보호 메서드며 추상 메서드다. 구현 클래스에서는 이 메서드에 적절한
논리를 제공해야 한다.
이 단순한 인터페이스는 실제로
Button
예제 수준에서는 필요하지 않다. 여기서
click
은 자신의
작업을 구체적인 보호된 메서드
updateUI
에 위임한다. 이는 세부적인 구현과 공개적인 추상화
를 분리하기 위해 자주 사용하는 형태다. 이는 ‘
4
인방’ 패턴 책에 있는 템플릿 메서드 패턴
Template
Method
Pattern
의 간단한 예라고 할 수 있다.
다음은 트레이트를 사용해서 리팩토링한 버튼이다.
//
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
=
{
/*
GUI
모양을
변경하는
논리
*/
}
}
예전에는 관찰을
Button
과 연결했지만, 이제는
Clickable
과 연결되어야 한다. 코드를 이런
방식으로 리팩토링하면, 실제로는 버튼을 관찰하려는 것이 아님을 분명히 알 수 있다. 우리가 관
심 있는 것은 버튼 눌림과 같은 이벤트를 관찰하는 것이다. 다음은 온전히
Clickable
을 관찰하
는 것에만 집중하는 트레이트다.
//
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
=
{
//
➊
super
.
click
()
notifyObservers
(
this
)
}
}
411
9
장
트레이트
➊
abstract
override
키워드에 유의하라. 이에 대해서는 다음에 설명한다.
구현은 예전의
ObservableButton
예제와 매우 비슷하다. 중요한 차이 하나는
abstract
키워
드다. 예전에는
override
만 있었다.
이 메서드를 자세히 들여다보라. 메서드는
super
.
click
( )
을 호출하지만, 이 경우
super
는 무
엇인가? 여기서는
Clickable
이 슈퍼타입인 것처럼 보인다. 그 안에는
click
메서드 선언이 들
어 있지만, 정의는 되어 있지 않다. 그 타입이 아니라면
Subject
일 수밖에 없는데, 그 안에는
click
메서드가 들어 있지 않다. 따라서
super
는 현재 시점에 실제 인스턴스와 연결될 수 없다.
그것이 여기서
abstract
가 필요한 이유다.
사실
super
는 이 트레이트가
click
메서드를 정의해주는
Button
과 같은 구체적 인스턴스
concrete
instance
안에 혼합될 때 연결된다.
abstract
키워드는 컴파일러에 (그리고 독자에게), 심지어
ObservableClicks
.
click
메서드의 본문이 있을지라도,
click
이 아직은 완전히 구현되지 않
았음을 알려준다.
NOTE
_
어떤 트레이트 안에 있는 메서드의 본문에서 부모 트레이트에 있는 다른 메서드를 호출하는데, 그
(부모 트레이트의) 메서드가 구체적인 구현은 되어 있지 않은 경우, 반드시 (부모 트레이트의 메서드를 호출하
는 본문이 있는) 메서드 앞에
abstract
를 붙여야 한다.
이 트레이트를
Button
과 그 안의 구체적
click
메서드에 대해 사용해보자.
//
src
/
main
/
scala
/
progscala2
/
traits
/
ui2
/
click
-
count
-
observer
.
sc
import progscala2
.
traits
.
ui2
.
_
import progscala2
.
traits
.
observer
.
_
//
Button
내에서
‘
click
’
을
오버라이딩할
필요는
없다
.
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
412
2
부
기본기 다지기
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
메서드를 재구현하지 않으면서
ObservableClicks
를 혼합할 수 있음에 유의하라. 그뿐 아니라 우리가 마우스 버튼 눌림을 관찰하고 싶지 않은 경
우라도,
Clickable
을 혼합하면 눌림을 지원하는
GUI
객체를 만들 수 있다.
이런 식으로 믹스인 트레이트를 활용해서 미세하게 기능을 합성하는 것은 꽤 강력하지만, 종종
그 사용이 지나칠 수 있다.
●
트레이트가너무많으면컴파일시간이오래걸린다.컴파일러가출력바이트코드를합성하기위해더많은
작업을해야하기때문이다.
●
스칼라독을볼때트레이트의목록이너무길면라이브러리사용자가지레겁먹을수있다.
두 번째 트레이트를 추가하면서 예제를 끝내자. 자바빈즈 명세(
http
://
bit
.
ly
/
1E8xIGM
)에는
‘거부할 수 있는
vetoable
’ 이벤트에 대한 개념이 들어 있다. 그런 이벤트의 경우, 리스너
listener
가 자
바빈의 상태를 변경하는 것을 거부할 수 있다. 그와 비슷한 것을 미리 지정한 횟수 이상 마우스
버튼이 눌리면 이벤트를 무시하는 트레이트를 만들어서 구현해보자. 금융 거래 등을 일으키는
버튼을 실수로 두 번 이상 누르지 못하게 막는 것을 구현하는 상상을 할 수 있을 것이다. 다음은
VetoableClick
트레이트다.
//
src
/
main
/
scala
/
progscala2
/
traits
/
ui2
/
VetoableClicks
.
scala
package progscala2
.
traits
.
ui2
import progscala2
.
traits
.
observer
.
_
trait VetoableClicks extends Clickable
{
//
➊
//
허용하는
최대
눌림
횟수의
기본값
val maxAllowed
=
1
//
➋
private var count
=
0
abstract override def click
()
=
{
Get 프로그래밍 스칼라: 실용적인 스칼라 활용법을 익히는 가장 확실한 실전 바이블 (2.11.x 버전 기반) now with the O’Reilly learning platform.
O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.