오늘은 Scala Mixin에 대한 내용을 소개해보려고 합니다.
Mixin은 클래스들을 합치는 데 사용되는 trait
(속성) 으로서, DRY 한 Scala언어가 되는데 크게 일조하고 있습니다.
소개
가장 쉽게, 혹은 흔하게 사용하는 mixin방법은 4 단계로 진행됩니다.
Step 1: 가장 먼저 super class A를 abstract class로 선언합니다.
Step 2: 이를 상속한 하위 클래스 B를 일반 클래스로 정의합니다. 이를 위해 부모 클래스인 abstract class에서 선언한 abstract형들을 명시합니다.
Step 3: Super class A에 대한 method들을 포함하는 trait C를 정의합니다.
Step 4: 최종적으로 사용할 일반 클래스 D는 일반 클래스 B를 상속하고, super class A에 대한 trait C를 포함하도록 mixin합니다.
아래 그림에서 AC는 abstract class를, C는 일반 class를, T는 trait를 나타냅니다.
아래 코드는 가장 흔하게 사용하는 mixin방법입니다. 클래스 A
는 abstract class
로서 String
형의 name
을 멤버로 갖습니다.
클래스 B
는 A
를 확장하고, name
은 John
으로 설정합니다.
트레이트 C
는 A
를 확장하는데, UpperName
메서드는 name
을 Upper Case로 만드는 함수 toUpperCase
를 호출한 결과를 반환합니다.
마지막으로 클래스 D
는 클래스 B
를 확장하고, 트레이트 C
의 속성 또한 가집니다.
여기서 사용되는 트레이트 C
를 Scala에서는 mixin으로 부릅니다.
Scala는 다중 상속을 지원하지 않습니다.
클래스는 extends
를 사용하여 하나의 클래스만 상속할 수 있지만, (하나의 super class만 가질 수 있고)with
를 사용하여 수많은 트레이트들을 mixin 할 수 있습니다. (많은 트레이트들; 속성들을 가질 수 있습니다)
abstract class A {
val name: String
}
class B extends A {
val name = "John"
}
trait C extends A {
def UpperName = name.toUpperCase()
}
class D extends B with C
val d = new D
println(d.name) // John
println(d.UpperName) // JOHN
응용 1
아래 자료는 https://docs.scala-lang.org/tour/mixin-class-composition.html
내용을 번역하고, 좀 더 자세한 설명을 추가한 것입니다.
AbsIterator
클래스는 abstract class
로서, 형이 정의되지 않은 abstract type형 멤버 T
와, 일반적인 iterator의 메서드로 구현할 hasNext와
next
메서드를 가지고 있습니다.
abstract class AbsIterator {
type T
def hasNext: Boolean
def next(): T
}
StringIter
클래스는 abstract 클래스인 AbsIterator
를 확장하고, String 인자 s를 받는 클래스로서, 이 클래스가 일반 클래스가 되기 위해서는 모든 abstract 멤버인 T
와 hasNext
, next
메서드가 구현되어야 합니다. StringIter 클래스를 일반 클래스로 만들기 위해,
(2)에서 T
는 Char
로 정의하고,
(4)에서 hasNext
는 내부 멤버인 i가 s의 길이 length
보다 작을 때 true
를 반환하며,
(5)에서 next
는 다음 문자 ch
를 반환합니다.
class StringIter(s: String) extends AbsIterator {
type T = Char // (2)
private var i = 0
def hasNext = i < s.length // (4)
def next() = { // (5)
val ch = s charAt i
i += 1
ch
}
}
RichIterator
속성은 AbsIterator
를 확장하는 속성으로서, 일반 클래스가 아닌 속성(trait)이므로 abstract 멤버인 T
, hasNext
, next를
구현할 필요가 없습니다. 따라서, abstract의 멤버만으로 새로운 메서드를 만들 수 있습니다.
새로운 foreach
메서드는 T
형을 받아 반환 값이 없는 함수 인자 f
를 받아서, hasNext
함수가 true
인 동안, f(next())
를 실행합니다.
trait RichIterator extends AbsIterator {
def foreach(f: T => Unit): Unit = while (hasNext) f(next())
}
RichStringIter
클래스는 StringIterator
클래스를 확장하고, RichIterator
속성도 가집니다. 따라서, StringIterator
는 super class가 되고, RichIterator
속성이 mixin 된 것입니다.
class RichStringIter extends StringIterator("Scala") with RichIterator
val richStringIter = new RichStringIter
richStringIter.foreach(println)
마무리
Scala나 Java, C/C++은 다중 상속을 지원하지 않습니다.
단일 상속만으로는, DRY (DO NOT REPEAT YOURSELF) 한 코드를 만드는데 매우 제한적입니다.
(With single inheritance we would not be able to achieve this level of flexibility.)
이러한 제약을 벗어나기 위해, Scala나 Java와 같은 언어들은,
클래스는 하나의 super class만 가질 수 있도록 extends
를 사용하여 하나의 클래스만 상속하게 만들었지만,
Java의 implement와 비슷하게, 수많은 트레이트; 속성들을 with를
사용하여 mixin 하게 함으로써, 매우 높은 수준의 코드 유연성을 확보하였습니다.
또한, 이러한 속성들은 일반 클래스가 아닌 최상단의 abstract class에서도 정의할 수 있게 함으로써, DRY 한 Scala언어가 되는데 크게 일조하고 있습니다.
'SW 설계 > 스칼라' 카테고리의 다른 글
스칼라: implicit 사용법 응용 (0) | 2021.10.27 |
---|---|
스칼라: implicit 기본 사용법 (0) | 2021.10.27 |
스칼라: Sealed, Final, Option, Try 클래스 (0) | 2021.09.25 |
스칼라: 부분적용함수 (Partially Applied Function), 부분함수 (Partial Function) 그리고 Case (0) | 2021.09.25 |