기본 콘텐츠로 건너뛰기

chap 10. trait

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를 십분 활용해서
클래스를 풍성하게 만들 수는 있을 듯 하다.

댓글

이 블로그의 인기 게시물

메일서버가 스팸으로 취급받을때

설치한 메일서버를 통해 발송되는 메일이 스팸으로 들어가는 경우가 더러 있다. 이게 한번 들어가기는 쉬운데, 빠져나오기는 드럽게 힘든것 같다... 본인의 경우에는 우선 국내서비스에는 별 무리 없이 들어간다. (naver,daum 등) 그런데 해외메일 그중 Gmail, Hotmail 에는 에누리없이 스팸으로 간주되고 있었다. Gmail같은 경우에는 그래도 스팸함으로 발송은 제대로 되는반면에 Hotmail같은경우에는 아예 수신자체가 안되는 경우도 더러있다.. ㅡ,.ㅡ; 제일 좋은 방법은 Gmail,Hotmail에 전화걸어서 우리 메일서버 IP white Ip로 등록해달라!!! 하면 좋지만, 얘네들은 걸어봤자 자동응답기고, 문의채널은 구글 그룹스 게시판이 전부다.. 본론으로 들어가서. 해외 메일이 차단될 경우 내 매일서버ip가 스팸ip로 등록되 버린 경우일 수 있다. (본인처럼. ㅎ) 이것부터 조회 해보고 싶으면 RBL(real-time blocking List) 체크를 해야 하는데, RBL체크 해주는 사이트는 꽤 많이 있고, 그중 좀 깔끔해 보이는곳 하나 소개. http://www.anti-abuse.org/ 메일서버ip 입력하고 조회해보면 쭈루룩 리스트가 나온다. 그 중 빨간불이 들어온 부분이 메일 서버가 스팸서버가 된 각종 이유들이다.ㅋ 본인의 경우 CBL 때문에 걸렸는데, 내용은 아래와 같다. This IP address is HELO'ing as  "localhost.localdomain"  which violates the relevant standards (specifically: RFC5321). 메일서버 도메인에 별다른 작업을 안해놓아서 "localhost.localdomain" 으로 설정되어있었다. 만약 CBL만 바로 테스트 해보고 싶으면 http://cbl.abusea

[javascript] 특정시간에만 함수 실행

특정시간에만 팝업을 띄우려면?? 특정시간에만 로그인을 막으려면?? 특정시간에만 할일은 의외로 참 많다. 방법? 딱히 없다. 현재시간 구해서 시작시간, 종료시간 사이에 있을때 시작하는 수밖엔. if ((현재시간 > 시작시간) && (현재시간 < 종료시간)){ .. 팝업노출(); 공사페이지 리다이렉트(); 기타등등(); .. } 자바스크립트로 작성하면 다음과 같다. var startdate = "2014012008" ; var enddate = "2014012418" ; var now = new Date (); //현재시간 year = now. getFullYear (); //현재시간 중 4자리 연도 month = now. getMonth () + 1 ; //현재시간 중 달. 달은 0부터 시작하기 때문에 +1 if ((month + "" ). length < 2 ){ month = "0" + month; //달의 숫자가 1자리면 앞에 0을 붙임. } date = now. getDate (); //현재 시간 중 날짜. if ((date + "" ). length < 2 ){ date = "0" + date; } hour = now. getHours (); //현재 시간 중 시간. if ((hour + "" ). length < 2 ){ hour = "0" + hour; } today = year + "" + month + "" + date + "" + hour; //오늘 날짜 완성. / / 시간비교 i

스레드 동기화1 - syncronized

구현 스레드를 구현하는 방법은 2가지다. 1. Thread 클래스를 extends 한다. 2. Runnable 인터페이스를 implements 한다. 뭐 사실 Thread는 생성자의 변수로 Runnable을 취한다. public Thread (Runnable target) { init( null, target , "Thread-" + nextThreadNum () , 0 ) ; } 그리고 Runnable 인터페이스는 run() 이라는 단일함수를 갖는 인터페이스이다. @FunctionalInterface public interface Runnable { /** * When an object implementing interface <code> Runnable </code> is used * to create a thread, starting the thread causes the object's * <code> run </code> method to be called in that separately executing * thread. * <p> * The general contract of the method <code> run </code> is that it may * take any action whatsoever. * * @see java.lang.Thread#run() */ public abstract void run () ; } 그렇기 때문에 람다식으로 표현이 가능한 것이다. 동기화 동기화는 동시에 같은자원의 접근을 제한하고자 할때 사용한다. 예를들어, 한 우물에서 물을 15번 길어야 하는 일을 해야할때 5명이서 3번만 하면 수고를 5배로 줄일수 있다. 그런데 우물은 하난데 동시에