관리 메뉴

기억하기 프로젝트

[스칼라] 클래스/객체/트레이드 본문

개발이야기/Scala

[스칼라] 클래스/객체/트레이드

sy89 2017. 3. 24. 16:58

4. 클래스와 객체

- 스칼라에서 객체를 생성하는 방법 하나는 클래스를 통한 인스턴스화, 나머지 하나는 object예약어를 통해 객체를 바로 생성하는 것


스칼라에는 static이 없다> 기존: 객체가 만들어있지 않은 상태에서 불가피하게 그 멤버를 바로 사용 가능하게 했음. (static)

but, 스칼라는 자바보다 더 객체지향적인 접근을 해서, 스칼라에서는 객체이든가, 아직 인스턴스화 되지 않은 클래스이든가 둘 중 하나임.
이러한 이유로 public class대신 object예약어를 통해 아예 처음부터 메모리에 객체를 생성해버리고, 컴파일러는 이 실물 안에 main 이라는 이름이 있다면 이를 프로그램의 시작점으로 생각하고 컴파일함. 객체지향의 철학이 훨씬 가미되었다고 할 수 있음.


스칼라는 생성자가 없다...> 자바 에서는 보통 클래스를 인스턴스화 할 때 생성자를 통해 값을 초기화 하지만,, 

but, 스칼라에서는 값의 초기화가 필요하다면 직접 멤버 변수를 명시할 때 값을 주면 된다.

object ex4_2 {
def main(args: Array[String]): Unit = {
var apple = new Fruit("사과")
println(apple.name)
}
}

class Fruit(input: String) {
var name = input
}


자바 에서는 class파일이 외부에 존재하며, 이 파일 안에 getter, setter, constructor, toString 메서드 등등 다 넣어줘야 하는데, 스칼라에서는 다음과같이 표현가능

class Vehicle(a: Int, b: Int)

(게다가 세미콜론도 빠질 수 있어서.. 간편하지만 넘나 적응 안되는것;;)

다만, 이렇게 축약방식으로 클래스를 만들면, 멤버 변수들이 모두 private 으로 생성되기 때문에 변수에 바로 접근할 수 없는 특징이 있음!

자바에서도 클래스 내 필드에는 private접근자를 사용하고, get메서드를 통해 필드에 접근을 하게 되는데, 스칼라에서도 이와 비슷하게 setter, getter메서드를 사용하여 접근할 수 있겠지만, (간결함을 지향하는 스칼라에는 맞지 않다고 함!) 이럴 때는 case 클래스를 사용할 수 있다.

case class Fruit(name: String)

이는, 기존에 선언했던 클래스에서 조금 더 확장되었는데, name이라는 멤버변수에 외부에서도 얼마든 접근가능하다.(기존에는 private접근자로 셋팅되지만.. case클래스는 그럼 public?)

또한, toString, hashCode, equals 메서드를 모두 알아서 생성해줌. 그리고 case class는 new를 생략 가능하게 함

case class Fruit(name: String)

object ex4_3 {
def main(args: Array[String]): Unit = {
var apple = Fruit("사과") // 요로케 new 생략가능 >_<
println(apple.name)
}
}


* 상속에 대해서도 알아보자!!!

기존에 구현된 클래스를 상속받아서 이미 선언된 변수를 다음과 같이 사용할 수 있다.

class User(name: String, age: Int, sex: Char) {
val sayName = println("제 이름은 " + name)
}

class PaidUser(name: String, age: Int, sex: Char, money: Int) extends User(name, age, sex) {
val showMoney = println(money + "원이 있습니다")
}

object ex4_4 {
def main(args: Array[String]): Unit = {
val user = new PaidUser("고말자", 20, 'W', 1000000)
user.sayName
user.showMoney
}
}


4.4  트레이트와 추상 클래스

트레이트 : '특성'이라는 뜻으로, 어떠한 객체에 추가될 수 있는 부가적인 하나의 특성을 말한다. (자바의 interface와 비슷하다고 함)

추상클래스 : 자바에서 사용하는 abstract class와 같다. (온전한 클래스에서 상속받아 사용해야 구현을 완료할 수 있음)

-> 트레이트, 추상클래스의 공통점은 그 자체로 인스턴스화가 가능하지 않다는 점~!

다음 예제 에서는, 각각의 특징을 정의해둔 트레이트를 상속받아 구현해보자.

trait Swimming {
def swim = println("수영을 합니다.")
}

trait Flying {
def fly = println("납니다")
}

trait Running {
def run = println("달립니다")
}

trait Eating {
def eat
}

// 위 클래스를 상속받는 구체적인 Animal 클래스
class Animal extends Flying with Swimming

object ex4_5 {
def main(args: Array[String]): Unit = {
val flyingWhale = new Animal
flyingWhale.fly
flyingWhale.swim
}
}


만약, 트레이트에서 구현된 메서드를 덮어버리고 새로운 로직 구현하고 싶을 경우에는 다음과 같이 재정의 해준다(override 예약어 반드시 이용)

class Animal extends Flying with Swimming {
// override def fly: Unit = super.fly
override def fly: Unit = println("오버라이드된 메서드")
}


4.5 트레이트 쌓기

- 동일한 메서드를 지닌 트레이트가 모두 상속이 되다가 서로 충돌이 나면, 스칼라는 구현 방식에 따라 한 가지를 선택하거나, 트레이트 쌓기를 통해 동일 이름의 메서드들의 우선순위를 결정해 쌓아두고 하나씩 실행하기도 한다.


object ex4_8 {
def main(args: Array[String]): Unit = {
val robot = new Mazinga
println(robot.shoot)
}
}

class Mazinga extends Robot with M16 with Bazooka with Daepodong

abstract class Robot {
def shoot = "뿅뿅"
}

trait M16 extends Robot {
override def shoot: String = "빵야"
}

trait Bazooka extends Robot {
override def shoot: String = "뿌왕뿌왕"
}

trait Daepodong extends Robot {
override def shoot: String = "콰르르르릉"

결과 :


결과로 볼 수 있듯이, 이름이 같은 메서드인 shoot()은, 추상클래스와 여러 트레이트를 계속해서 덮어쓰다가 결국에 남는 것은 Daepodong 트레이트에 있는 shoot()이 실행된다.

그 외에, shoot()을 가능한 한 다양하게 사용할 수 있는 방법도 있다. 자신의 상위 클래스를 super로 호출하여 사용하는 방법이다.

object ex4_9 {
def main(args: Array[String]): Unit = {
val robot = new SuperMazinga
println(robot.shoot)
}
}

class SuperMazinga extends AnotherRobot with AnotherM16 with AnotherBazooka with AnotherDaepodong

abstract class AnotherRobot {
def shoot = "뿅뿅"
}

trait AnotherM16 extends AnotherRobot {
override def shoot: String = super.shoot + "빵야"
}

trait AnotherBazooka extends AnotherRobot {
override def shoot: String = super.shoot + "뿌왕뿌왕"
}

trait AnotherDaepodong extends AnotherRobot {
override def shoot: String = super.shoot + "콰르르르릉"
}

결과 : 

Daepodong은 Bazooka를 부르고, Bazooka는 M16을, M16은 Robot클래스의 기본 shoot을 호출하기 때문에 위와 같은 결과를 볼 수 있다.


혹은, 다른 방법으로 다음과 같이 상속받는 클래스상에서 재정의 해도 무방하다.

class Mazinga extends Robot with M16 with Bazooka with Daepodong {
override def shoot: String = "클래스상에서 재정의!"
}


스칼라에서는 트레이트가 비중있는 존재인데, 상당수의 라이브러리의 내부가 트레이트로 구현되어 있다고 한다. 스프링 에서도 interface를 정의하여 확장 가능하도록 하는 것과 비슷한 맥락인지..? 앞으로 사용해봐야 알 것 같다.ㅎㅎ

'개발이야기 > Scala' 카테고리의 다른 글

스칼라 참고 자료  (0) 2017.04.10
[스칼라] 함수  (0) 2017.03.30
[스칼라] 중첩문  (0) 2017.03.24
[스칼라] 시작 - 변수  (0) 2017.03.22