기억하기 프로젝트
[스칼라] 클래스/객체/트레이드 본문
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를 정의하여 확장 가능하도록 하는 것과 비슷한 맥락인지..? 앞으로 사용해봐야 알 것 같다.ㅎㅎ