Trait

2015. 4. 27. 18:13Programming/Scala

스칼라에는 자바의 Interface와 비슷한 trait라는 것이 존재한다.

비슷하면서도 다른 게 바로 자바의 Interface와 스칼라의 trait인데, 우선 자바의 Interface는 Interface 내에 정의만 가능하고 구현은 Interface를 상속받은 클래스 내에서 구현을 해야 했다.

그에 반해 스칼라의 trait는 정의 뿐 아니라 실제 구현도 가능하기 때문에 사용자가 직접 구현을 하지 않아도 되는 장점을 가지고 있다.

또한 Trait는 다중 상속 개념에 대해서 자바나 C++보다 합리적인 방안을 제시하고 있다. 


이에 대한 설명은 차츰 진행하기로 하며, 우선 trait를 어떻게 사용하는지에 대해 살펴보자.


trait Philosophical{

    def philosophize(){

        println("philosophize")

    }

}


사용법은 간단하다 class나 object 대신에 trait란 키워드를 쓰면 trait가 정의된다.


정의된 trait는 아래와 같이 사용할 수 있다.


class Frog extends Philosophical{

    override def toString = "green"

}    


trait에서 안되는 것이 있는데 바로 생성자에 파라미터를 전달하는 것이다.


trait Philosophical(x: Int, y: Int)


위와 같이 선언하면 컴파일 시 에러가 발생한다.


이번에는 trait를 여러 개 선언하고 선언된 trait를 사용하는 예제를 살펴보자.


abstract class IntQueue{

    def get():Int

    def put(x:Int)

}

trait Doubling extends IntQueue{

    abstract override def put(x:Int) { super.put(2*x)}

}

trait Incrementing extends IntQueue{

    abstract override def put(x:Int) { super.put(x+1) }

}    


위와 같이 trait를 선언했다고 가정하자. 여기서 못보던 문법들이 등장했는데 첫 번째로 trait에서의 extends

어떤 부모 클래스를 상속받은 trait일 경우에는 IntQueue를 상속받은 클래스에서는 해당 trait가 사용이 가능하다.

예를 들어, class A extends IntQueue가 있고, class B extends Rations 이렇게 두 개의 클래스가 있을 경우에

A클래스에 대해서만 trait의 사용이 가능하다.


두번째는 함수 내에 super의 사용이다. 스칼라에서는 위와 같이 사용함으로써 부모 매서드에 대해 해당하는 값을 보낼 수가 있다.

여기서 의문이 발생하는데 만약 Doubing이라는 trait와 Incrementing이라는 trait를 동시에 사용했을 경우에 put의 동작은 어떻게 될까??


val queue = (new BasicIntQueue with Incrementing with Doubling)    

queue.put(1)

queue.get()

????


위와 같은 경우일 것이다. 위와 같은 경우에는 과연 어떻게 동작할까??

스칼라에서는 linearization이라고 해서 trait가 상속(상속이란 말 보단 mix in이란 표현을 씀)되었을 때 호출 순서를 선형적으로 정한다. 위의 예제 같은 경우에는 BasicIntQueue가 가장 먼저 호출이 될테고, 그 다음에 trait의 가장 오른쪽에 있는 것부터 차례대로 호출이 된다.

BasicIntQueue -> Doubling -> Incrementing -> IntQueue -> AnyRef -> Any


AnyRef와 Any는 스칼라에서 가장 최상위 클래스로써 거의 대부분의 객체가 Any 클래스를 상속하고 있다.

위와 같은 호출 순서가 일어나기 때문에 다중 상속에 유리한 장점을 가지고 있다.

이러한 trait의 강력한 기능 덕분에 cake pattern이라는 디자인 패턴의 사용이 가능하다.

cake pattern이란 기존에 정의된 클래스 위에 trait로 기능을 추가로 올림으로써 케이크에 케이크를 하나 더 얹는 것처럼 기능을 구현하는 것을 말한다. 

위의 예제에서 보면 new BasicIntQueue란 클래스 위에 동적으로 Incrementing 기능과 Doubling 기능을 추가하여 기능을 확장하였다.


주의할 점은 trait 순서에 따라 결과 값이 달라질 수가 있다.

예를 들어

BasicIntQueue with Incrementing with Doubling 과 BasicIntQueue with Doubling with Incrementing을 선언했을 때의 put(1)을 한 값은 각각 2와 4가 된다.