trait.
일단 java의 interface 같은 놈인듯 한데, 뭔가 다른게 있는것 같다.
1. 다중상속의 문제
아래의 Student, Employee개의 부모클래스가 있고,
이둘을 상속하는 TeachingAssistant 라는 클래스가 있다.
그런데 문제는 두개의 부모클래스가 id라는 공통되는 변수를 가진다면
상속받는 클래스에서는 어떤 id를 택하게 될까라는 고민거리가 생긴다.
또 다이아몬드상속문제(Student와 Employee 역시 Person이라는 클래스를 상속한 서브클래스였다면)가 발생했을시 어떻게 할까,
C++에서는 '가상 베이스 클래스' 라는 기능을 사용하고,
자바에서는 다중상속을 지원하지 않고,
인터페이스는 추상 메소드만 가질수 있고 필드는 가질 수 없게 한정하였다.
scala에서는 이러한 다중상속문제 다루기위해 trait을 사용한다.
2. trait가 있는 오브젝트
trait는 자바의 인터페이스 기능을 수행한다.
인터페이스처럼 함수 구현부는 없이 사용할 수도 있지만,
인터페이스와는 달리 일반클래스처럼 함수를 구현해놓을 수도 있다.
그리고 믹스인이 된 클래스들은 trait이 변경되면 믹스인한 모든 클래스가 재 컴파일 되야하니 알고쓰자.
오브젝트를 생성할때 trait를 추가할 수 있다.
3. 레이어드 trait
단계별로 어떤값을 변화시키는 작업이 필요할때,
trait를 여러개 서로 호출하며 사용해야 하는 경우가 있다.
두개의 trait가 있다.
그리고 이 trait들을 순서를 바꿔서 호출해보면
이렇게 생성된 객체의 log를 던저보면
참고로 여기서 ConsoleLogger가 log를 구현하고 있다.
만약 구현이 없는 경우에는 log 함수는 추상메소드이므로 abstract override를 같이 표시해야 한다.
4. trait생성순서
위의 경우 실행순서는 이렇다.
ㄱ. Logged (첫번째 trait의 부모)
ㄴ. ConsoleLogger(첫번째 trait)
ㄷ. TimestampLogger
ㄹ. ShortLogger
ㅁ. SavingAccount
5. trait필드초기화
trait클래스의 유일한 기술적 차이는 trait는 생성자가 없다.
이는 trait의 필드를 초기화하는 익명클래스를 사용해서 해결할 수 있을듯 하다.
이렇게하면 될듯 싶은데, 문제가 있다.
FileLogger생성자는 서브클래스 생성자보다 먼저 실행된다.
여기서 서브클래스를 인지하기는 어렵다.
new문은 FileLogge trait가 있는 SavingAccount를 확장한 익명클래스의 인스턴스를 생성한다.
Filename은 익명서브클래스에서 할당이 되야 하지만,
FileLongger 생성자에서 넉포인터 오류가 발생한다.
이는 상속에서 생성자 조기정의와 비슷한 방법으로 해결한다.
결과적으로 trait는 extends 뒤에 두개의 클래스를 적을 수는 없다.
단. 생성자가 없는 클래스같은 인터페이스인 trait를 십분 활용해서
클래스를 풍성하게 만들 수는 있을 듯 하다.
일단 java의 interface 같은 놈인듯 한데, 뭔가 다른게 있는것 같다.
1. 다중상속의 문제
아래의 Student, Employee개의 부모클래스가 있고,
이둘을 상속하는 TeachingAssistant 라는 클래스가 있다.
class Student{ def id: String = ... } class Employee{ def id: String = ... } class TeachingAssistant extends Studet, Employee{ .. }
그런데 문제는 두개의 부모클래스가 id라는 공통되는 변수를 가진다면
상속받는 클래스에서는 어떤 id를 택하게 될까라는 고민거리가 생긴다.
또 다이아몬드상속문제(Student와 Employee 역시 Person이라는 클래스를 상속한 서브클래스였다면)가 발생했을시 어떻게 할까,
C++에서는 '가상 베이스 클래스' 라는 기능을 사용하고,
자바에서는 다중상속을 지원하지 않고,
인터페이스는 추상 메소드만 가질수 있고 필드는 가질 수 없게 한정하였다.
scala에서는 이러한 다중상속문제 다루기위해 trait을 사용한다.
2. trait가 있는 오브젝트
trait는 자바의 인터페이스 기능을 수행한다.
인터페이스처럼 함수 구현부는 없이 사용할 수도 있지만,
trait Logger { def log(msg: String) }
인터페이스와는 달리 일반클래스처럼 함수를 구현해놓을 수도 있다.
trait ConsoleLogger extends Logged { def log(msg:String){println(msg)} }그리고 후자의 경우는 trait이 클래스의 믹스인 되었다 라는 표현을 사용한다.
그리고 믹스인이 된 클래스들은 trait이 변경되면 믹스인한 모든 클래스가 재 컴파일 되야하니 알고쓰자.
오브젝트를 생성할때 trait를 추가할 수 있다.
trait Logged { def log(msg: String){} } class SavingAccount extends Logged{ log ("Insufficient funds") } val acct = new SavingAccount with ConsoleLogger
3. 레이어드 trait
단계별로 어떤값을 변화시키는 작업이 필요할때,
trait를 여러개 서로 호출하며 사용해야 하는 경우가 있다.
두개의 trait가 있다.
trait TimestampLogger extends Logged{ override def log(msg: String): Unit ={ super.log(new java.util.Date() + " " + msg) } } trait ShortLogger extends Logged{ val maxLength = 15 override def log(msg:String): Unit ={ super.log( if(msg.length <= maxLength) msg else msg.substring(0, msg.length -3 ) + "..." ) } }
그리고 이 trait들을 순서를 바꿔서 호출해보면
val acct1 = new SavingAccount with ConsoleLogger with TimestampLogger with ShortLogger val acct2 = new SavingAccount with ConsoleLogger with ShortLogger with TimestampLogger
이렇게 생성된 객체의 log를 던저보면
Thu Jun 18 18:46:29 KST 2015 acct1 //acct1 Thu Jun 18 18:46:29 KST 2015 ac... //acct2가장 뒤의 trait부터 실행 된다는걸 알 수 있다.
참고로 여기서 ConsoleLogger가 log를 구현하고 있다.
만약 구현이 없는 경우에는 log 함수는 추상메소드이므로 abstract override를 같이 표시해야 한다.
4. trait생성순서
val acct = new SavingAccount with ConsoleLogger with TimestampLogger with ShortLogger
위의 경우 실행순서는 이렇다.
ㄱ. Logged (첫번째 trait의 부모)
ㄴ. ConsoleLogger(첫번째 trait)
ㄷ. TimestampLogger
ㄹ. ShortLogger
ㅁ. SavingAccount
5. trait필드초기화
trait클래스의 유일한 기술적 차이는 trait는 생성자가 없다.
이는 trait의 필드를 초기화하는 익명클래스를 사용해서 해결할 수 있을듯 하다.
trait FileLogger extends Logger{ val filename : String val out = new PrintStream(filename) def log(msg:String){out.println(msg); out.flush()} }
val acct = new SavingAccount with FileLogger{ val filename = "myapp.log" }
이렇게하면 될듯 싶은데, 문제가 있다.
FileLogger생성자는 서브클래스 생성자보다 먼저 실행된다.
여기서 서브클래스를 인지하기는 어렵다.
new문은 FileLogge trait가 있는 SavingAccount를 확장한 익명클래스의 인스턴스를 생성한다.
Filename은 익명서브클래스에서 할당이 되야 하지만,
FileLongger 생성자에서 넉포인터 오류가 발생한다.
이는 상속에서 생성자 조기정의와 비슷한 방법으로 해결한다.
val acct = new{ val filename = "myapp.log" } with SavingAccount with FileLogger class SavingAccount extends { val filename = "savings.log" } with Account with FileLogger{ ... }
trait FileLogger extends Logger{ val filename : String lazy val out = new PrintStream(filename) def log(msg: String) { out.println(msg)} }
결과적으로 trait는 extends 뒤에 두개의 클래스를 적을 수는 없다.
단. 생성자가 없는 클래스같은 인터페이스인 trait를 십분 활용해서
클래스를 풍성하게 만들 수는 있을 듯 하다.
댓글
댓글 쓰기