SW 설계/스칼라

스칼라: Mixins

yztech 2021. 10. 9. 14:47
반응형

오늘은 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방법입니다. 클래스 Aabstract class로서 String형의 name을 멤버로 갖습니다.
클래스 BA를 확장하고, nameJohn으로 설정합니다.
트레이트 CA를 확장하는데, 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 멤버인 ThasNext, next 메서드가 구현되어야 합니다. StringIter 클래스를 일반 클래스로 만들기 위해,
(2)에서 TChar로 정의하고,
(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언어가 되는데 크게 일조하고 있습니다.

반응형