ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [스칼라] 트레이트
    스칼라 2022. 2. 20. 12:56

    Programming in scala 4th edition 12장을 읽고 정리한 내용입니다.

     

    0. 트레이트

    트레이트는 코드 재사용을 쉽게 하기 위해 스칼라에서 제공하는 개념이다. 자바 인터페이스랑 유사하다고 생각하면 처음 접근할때 쉽다. 트레이트는 메서드와 필드로 이루어지고 클래스에서는 여러개의 트레이트를 Mixin(믹스인) 해서 기능을 구현할 수 있다. 트레이트의 핵심은 다음 두가지다

    • 간결한 인터페이스를 확장해 풍부한 인터페이스를 만듬
    • 쌓을수 있는 변경을 정의함

    1. 트레이트 동작 원리

    trait을 정의하는 방법은 클래스를 정의하는 방법과 같다.

    trait Philosophical {
      def philosophize() = {
        println("나는 메모리를 사용한다, 고로 존재한다!")
      }
    }
    

    트레이트 정의 후에는 extends나 with를 사용해서 클래스에 조합해서 사용할 수 있다.

    class Person extends Philosophical {
      val name = "Kim"
    }
    
    val p = new Person
    p.philosophize() // 나는 메모리를 사용한다, 고로 존재한다!

     

    트레이트는 타입으로 활용할 수도 있다. 

    val philosophical: Philosophical = new Person
    philosophical.philosophize()
    // print(philosophical.name) // name은 Philosophical의 멤버가 아니므로 출력 불가

     

    어떤 클래스를 상속받은 클래스와 트레이트의 조합은 아래와 같이 사용할 수 있다.

    class Human
    trait Philosophical {
      def philosophize() = {
        println("나는 메모리를 사용한다, 고로 존재한다.")
      }
    }
    
    class Programmer extends Human with Philosophical
    val programmer = new Programmer
    programmer.philosophize() // 나는 메모리를 사용한다, 고로 존재한다.

     

    trait과 class는 두 가지 차이가 있다. 

    • trait은 클래스 파라미터를 가질 수 없음
    • 클래스는 super 호출을 정적으로 바인딩하지만 trait은 동적으로 바인딩한다.

     

    2. Ordered 트레이트

    trait을 이용하면 유사하게 동작해야 하는 기능들을 관리하기가 쉽다. 그래서 trait을 이용하면 풍부한 인터페이스를 제공하기가 용이하다.

    예를들어서 비교 연산에서는 ~보다 작은가, ~보다 작거나 같은가 기능을 제공할 때 <와 <=을 사용한다. 간결한 인터페이스라면 사용자가 작거나 같은가를 사용할 때 (x < y) || (x == y)와 같은 형태로 사용하도록 할 것이다. 풍부한 인터페이스라면 <= 연산자를 직접 제공할 수 있어야 한다. 아래는 Ordered 트레이트를 사용하지 않고 비교 연산자를 구현한 코드이다.

    class Rational(n: Int, d: Int) {
      val numer = n
      val denom = d
      def < (that: Rational) = this.numer * that.denom < this.denom * that.numer
      def > (that: Rational) = that < this
      def <= (that: Rational) = (this < that) || (this == that)
      def >= (that: Rational) = (this > that) || (this == that)
    }

    >, <=. >= 의 구현은 사실상 <의 구현만으로 만든 연산자이다. >, <=, >=을 미리 구현해두고 사용자가 < 만 구현하면 다른 비교연산자가 모두 동작 하도록 해주면 어떨까. 이런 기능을 Ordered 트레이트가 제공한다. Ordered 트레이트에서는 compare메서드만 구현하면 나머지 비교 메서드를 사용할 수 있다.

    class Rational(n: Int, d: Int) extends Ordered[Rational] {
      val numer = n
      val denom = d
    
      def compare(that: Rational) = this.numer * that.denom - this.denom * that.numer
    }
    
    val r1 = new Rational(1, 2)
    val r2 = new Rational(1, 10)
    
    r1 < r2 // 출력 1/2가 1/10보다 크므로: false
    r1 > r2 // true

     

    3. 트레이트를 이용해 변경 쌓아올리기

    트레이트의 장점 두번째는 쌓을 수 있는 변경이다. 정수로 된 큐가 있다고 해보자.

    abstract class IntQueue {
      def get(): Int
      def put(input: Int): Unit
    }
    class BasicIntQueue extends IntQueue {
      private val buffer = new ArrayBuffer[Int]
    
      def get(): Int = buffer.remove(0)
    
      def put(input: Int): Unit = { buffer += input }
    
    }
    
    val queue = new BasicIntQueue
    queue.put(1)
    queue.put(2)
    queue.put(3)
    
    print(queue.get())
    print(queue.get())
    print(queue.get())

    ArrayBuffer를 이용한 BasicIntQueue의 모습이다.

    여기에 queue에 삽입할 때 인풋을 2배하여 집어넣는 트레이트를 구현해보자.

    trait Doubling extends IntQueue {
      abstract override def put(x: Int) = {super.put(2 * x)}
    }

    이 트레이트에는 두 가지 특징이 있다.

    • 추상 클래스인 IntQueue를 상속함: 이 트레이트는 IntQueue의 구현체에만 믹스인 될 수 있다.
    • put에서 super를 호출함: 이 트레이트가 상속받은것은 IntQueue인테 super.put을 호출하고 있다.

    두 번째 특징은 오타가 아니다. 스칼라의 트레이트에서는 호출이 동적으로 바인딩된다. 여기서의 super는 추상 클래스 IntQueue를 말하는 것이 아니다. 실행 시점이 되어야 어떤 클래스의 put이 호출되는지 알 수 있다. 트레이트 안에서 super를 사용하기 위해서는 반드시 abstract override 키워드를 붙여주어야 하고 abstract override 키워드가 붙은 메서드를 구현한 클래스에만 믹스인 할 수 있다.

     

    val doubleQueue = new BasicIntQueue with Doubling
    doubleQueue.put(10)
    doubleQueue.get()

    위의 예제처럼 트레이트는 new 키워드와 함께 사용될 수도 있다.

     

    쌓을 수 있는 변경 예제를 위해서 아래 두 개의 trait를 구현한다.

    trait Incrementing extends IntQueue {
      abstract override def put(input: Int): Unit = { super.put(input + 1)}
    }
    
    trait Filtering extends IntQueue {
      abstract override def put(input: Int): Unit = {
        if(input > 0) {
          super.put(input)
        }
      }
    }
    val queue = (new BasicIntQueue with Incrementing with Filtering)
    queue.put(0)
    queue.put(1)
    queue.put(2)
    queue.get() // 2
    queue.get() // 3

    위의 예제는 Filtering, Incrementing 트레이트를 믹스인한 큐의 모습이다. 이 큐에 0, 1, 2를 순서대로 집어넣으면 2, 3이 출력된다.

    val queue = (new BasicIntQueue with Filtering with Incrementing)
    queue.put(0)
    queue.put(1)
    queue.put(2)
    queue.get() 1
    queue.get() 2
    queue.get() 3

    위의 예제는 Filtering, Incrementing의 순서를 뒤바꾼 결과이다. 0, 1, 2를 집어넣었을때 1, 2, 3이 출력된다.

    이렇게 트레이트를 여러개 믹스인하면 맨 뒤의 트레이트부터 호출된다.

     

     

    '스칼라' 카테고리의 다른 글

    [스칼라] 리스트  (0) 2022.02.22
    [스칼라] 케이스 클래스와 패턴 매치  (0) 2022.02.21
    [스칼라] 스칼라 계층구조  (0) 2022.02.13
    [스칼라] 상속과 구성  (0) 2022.02.03
    [스칼라] 흐름 제어 추상화  (0) 2022.02.01

    댓글

Designed by Tistory.