본문 바로가기

Programming/Scala

Scala - Java 의 interface 의 가려운 부분을 긁어주는 trait






오늘은 뭐에 feel 받았는지, 스칼라 공부를 하면서 글을 쓰고 싶은 날이다. 벌써 스칼라 관련 글을 3개 연속으로 썼다... :)


스칼라 언어에는 Java언어의 interface에 해당하는 trait이라는 녀석이 있다.

이 녀석은 Programming in Scala 2nd Edition 에서는 아래와 같이 소개가 되어있다.


Traits are a fundamental unit of code reuse in Scala. A trait encapsulates method and field definitions, which can then be reused by mixing them into classes. Unlike class inheritance, in which each class must inherit from just one superclass, a class can mix in any number of traits.


trait 은 스칼라 언어에서 필수적인 요소 중에 하나인데, 이를 이용해서 코드의 재사용을 극대화 할 수 있다. 클래스의 상속 관계와는 다르게 여러개의 trait 을 섞어서 사용할 수 있다.


여기서 섞어서 사용한다는 단어가 중요하다. Java언어의 interface 는 이 interface를 상속받는 클래스들의 뼈대를 제공해 주지만 정작 메소드 구현은 interface를 상속받는 클라이언트 클래스를 구현하는 개발자가 전부 구현해야 한다. 하지만 Scala언어에서의 trait은 이와 다르게 구체적인 함수가 구현되어 있어서 이를 사용하는 개발자는 특별히 mix(섞어서) 함수를 구현하지 않고 바로 사용할 수 있다는 장점이 있다. 


그래서 책에는 아래와 같은 설명이 덧붙여서 있다.  

One major use of traits is to automatically add methods to a class in terms of methods the class already has. That is, traits can enrich a thin interface, making it into a rich interface. 


그래서 Scala언어에서의 trait은 Java언어의 interface의 부족한 부분을 풍부하게 해주는 역할을 한다.


trait 은 아래와 같이 선언한다. (책의 예제에서 발췌함)

trait Philosophical {
    def philosophize() {
      println("I consume memory, therefore I am!")
    }


다른 클래스에서 이 trait을 mix 해서 사용하는 방법은 아래와 같다.

class Frog extends Philosophical {
    override def toString = "green"


Java언어에서는 implements이지만 Scala언어에서는 그냥 extends 이다.


여려개의 trait을 사용하는 방법은 아래와 같다.

class Animal

trait HasLegs

class Frog extends Animal with Philosophical with HasLegs { override def toString = "green"


그렇다. with 를 사용하면 된다! :)


여기까지 간단한 trait 문법을 살펴보았다.

이제부터 정말 중요한 내용을 다루겠다.


Scala언어에서의 trait이 Java언어의 interface 그리고 다른 객체지향 언어(C++, C#등등)와의 가장 큰차이점은 trait은 바로 stackable modification 을 해주기 때문에 클라이언트 클래스에서는 클래스를 사용함에 있어서 선형화(Linearization)을 달성할 수 있다는 것이다.


무슨 이야기인고 하니 아래와 같은 예제를 살펴보자.


Queue 데이터구조와 유사한 일을 하는 클래스를 구현해 보자. 추상 클래스로 정의하여 상속받은 클래스들이 구체적인 함수를 구현하도록 하자.




그리고 아래와 같이 ArrayBuffer 클래스를 사용하여 Queue의 get과 put 함수를 구현하자.

그리고 10, 20을 put 을 이용하여 Queue 에 넣은 다음 다시 get 을 이용하여 빼내자. :)


자 대충 구현한 Queue, exception 처리는 부족하지만 그럭저럭 잘 작동한다. :)



다음은 조금 더 재미있는 일을 해 보자.

put을 하기 전에 그 값을 두배를 해서 입력하는 Doubling trait 을 정의한다.




그 다음은 값을 하나 증가시키는 일을 하는 trait 을 정의




다음은 음수가 들어오면 입력하지 않게 하는 filtering 기능을 수행하는 trait을 정의




다음은 이러한 trait 들의 기능을 테스트 하는 인스턴스 선언




실제로 get 함수를 호출하면 아래와 같이 음수가 아닌 1, 2만 얻을 수 있다. 재미있다. 그렇지 않은가? 그리고 순서도 재미있다. 값을 증가하여 filtering 했으면 값이 3개가 들어가야 하는데, 그게 아니라 filtering이 먼저 실행되고, 그 다음 값을 하나를 증가 시킨 것이다. 이제 trait 이 왜 stackable modification 기능을 제공하는 지 이해가 될 것이다.




그리고 마지막으로 아래와 같은 인스턴스를 생성해 보자.




그리고 테스트를 해 보자.



stackable modification 처럼 filtering -> doubling -> incrementing 이 된 것을 알 수 있다.

Queue 에는 2개의 인자 밖에 없는데 한 번 더 구하려고 해서 exception 이 발생하는 것을 알 수 있다.


이렇게 trait 을 이용하면 stackable modification 을 이용하여 특정 클라이언트 클래스에서 자신이 원하는 행동을 적용할 수 있다. 이는 다중 상속이 허용되는 C++과 하나의 부모만을 허용하는 Java언어에서는 제공하지 않는 Scala 만의 기능이다. Scala 언어는 trait 을 이용하여 Java언어의 가벼운 interface 에 특정 구현된 함수를 이용하여 내용을 풍부하게 할 뿐만 아니라, stackable modification 을 제공하여 클라이언트 클래스가 상속관계를 이용하여 특정 함수를 override 하여 여러가지 코드를 실행하게 하는 방식이 아니라 선형적으로(linear) 특정 기능들을 수행하게 한다. 이는 프로그램을 작성할 때 특정 코드가 수정되어도 그 코드를 상속받는 클라이언트 클래스는 많은 상속 관계속에서 코드의 구조를 살펴볼 필요 없게 수정된 trait 만 살펴보도록 하는 개발의 편의성을 제공한다.