스칼라 객체의 Class와 Object 그리고 apply method

View: 184 0 0
작성자: 달빛제이크
카테고리: Scala Language
발행: 2024-05-23 수정 2024-06-21

안녕하세요. 달빛제이크입니다.

지난 글에서 스칼라 언어의 세 가지 특징에 대해서 알아 봤는데요. (https://code-snippet.kr/blog/all/49)

이번 글에서는 이 세 가지 특징 중에 스칼라의 객체 지향에 대해서 알아볼게요.

스칼라가 제공하는 객체 지향의 특성을 세부적인 부분까지 이 글에서 모두 다룰 수는 없고, 큰 특징들에 대해서 먼저 소개를 할 거에요.

큰 특징들에 대해서 먼저 이해하고 스칼라가 제공하는 기능들을 하나 씩 알아가다 보면 아마도 스칼라의 Detail에 놀라고 더욱 스칼라 언어가 재미있어 질 거에요.

객체 지향 프로그래밍 언어를 대표하고 객체 지향의 시대를 연 언어가 바로 자바 입니다.

그래서 객체 지향의 특징들을 이야기하게 되면 자바랑 자연스럽게 비교하게 되는 데요.

필요에 따라서 자바를 언급하게 될 거고, 사용하는 용어에 대해서는 되도록 원어를 그대로 사용하려고 해요.

저는 한글 용어가 더 어렵게 느껴지고 원어 자체가 더 직관적인 것 같아요. 스칼라 커뮤니티에서 소통을 하려고 해도 영어를 사용하니까 국제적으로 통용되는 용어를 익히는 게 좋을 것 같아요.

1. Class와 Object

스칼라의 객체 지향에 대해 Class (클래스), Object (객체), Instance (인스턴스)를 먼저 구분해 볼게요.

  • Class (클래스)

Class는 객체의 구조와 행동을 정의하는 템플릿을 의미합니다. 보통 객체의 설계도라고 이야기하는 데요.

Class 그 자체는 객체의 대상이 되는 Data와 그 데이터에 대한 동작을 정의하는 코드 블록 정도로 이해하면 좋을 것 같아요.

  • Object (객체)

Object에 대해서는 일반적인 용어로 객체라고 이야기하는데요, Scala에서는 일반적인 Object라는 용어와 키워드로서의 object를 구분해야 합니다.

객체라는 용어가 Class와 Instance를 설명할 때 기본 단위로 많이 사용되고 있는데, 그 용어의 의미가 명확하게 하나의 뜻을 가지고 있다기보다는 상황에 따라 약간 씩 다른 의미로 사용하고 있는 것 같아요.

제가 이해하는 가장 일반적이고 기본이 되는 객체의 의미는 데이터 (Field)와 그 데이터에 대한 동작 (Method)을 함께 포함하는 독립적인 개체입니다.

이 객체를 Class를 통해서 정의하고 메모리에 올려서 Instance로 만듭니다.

Instance 자체를 객체라고 부르기도 하는 데, 정확하게 표현하면 Instance란 Class를 바탕으로 구체적인 Data가 정의되어 메모리에 올려져 생성된 객체를 의미합니다.

Scala에서 Object는 Class와 대등한 Code Block을 의미합니다. 다만, Class가 객체의 설계도 역할을 담당한다면, Object는 Java의 Static Field와 Method를 한 데 모아서 독립된 코드 블록을 만듭니다.

Scala의 Object는 Singleton Object와 Companion Object로 구분합니다.

Singleton Object는 짝꿍이 되는 Class 없이 독립적으로 존재하는 Object를 의미하고 대표적인 Singleton Object로는 main method를 포함한 Object가 있습니다.

Companion Object는 짝꿍이 되는 Class가 존재하며 Class 명과 동일한 이름을 갖습니다. 보통 Class의 Data를 처리할 때 필요로 하는 함수들을 포함하고 있으며 Class Member들과 Object Member들은 서로 공유됩니다.

  • Instance (인스턴스)

앞에서 설명한 것과 같이 Class를 바탕으로 구체적인 Data가 정의되어 메모리에 생성된 객체를 의미합니다. 한글 용어로 보통 객체라고 부르기 때문에 객체가 다의어처럼 사용하게 된 원인이 된 녀석입니다.

예제를 쭉 적어 볼게요...


// class의 정의와 Instance 생성
class Person(val name: String, val age: Int):
  def greet(): String = s"Hello, my name is $name and I am $age year old."

val person = new Person("John", 30)
println(person.greet())        // "Hello, my name is John and I am 30 years old."

// Singleton Object
object Main:
  def main(args: Array[String]): Unit = println("This object is a Singleton Object.")

// Companion Object
class Person(val name: String, val age: Int)

object Person:
  def apply(name: String, age: Int): Person = new Person(name, age)

val person = Person("John", 30)
  • apply method

앞의 예제에서 companion object에 apply method가 있는 것이 눈에 띕니다.
보통 객체를 Instance 화 할 때 new라는 키워드를 사용하는데, 스칼라에서는 new 라는 키워드를 사용하지 않고도 동일한 기능을 수행 할 수 있습니다.

class Person(name: String, age: Int)
val person = new Person("Bob", 32)

class Person(name: String, age: Int)
object Person:
  def apply(name: String, age: Int): Person = new Person(name, age)

val person = Person("Peter", 28)        // Person.apply("Peter", 28)와 동일

apply는 Companion object에 작성되어 마치 object의 이름을 함수처럼 사용할 수 있게 하는 편의 도구라고 보시면 됩니다. 즉, Person.apply("Peter", 28)에서 apply를 생략하고 Person("Peter", 28)로 사용을 할 수 있습니다.

이는 List, Set, Map과 같은 Collection을 만들 때 주로 사용합니다.

// 내부 구현 object (예시)
object List:
  def apply[A](elems: A*): List[A] = elems.toList

// List 생성
val list = List(1,2,3,4,5)        // List.apply(1,2,3,4,5)와 동일, apply 생략

List의 apply method를 통해 List를 쉽게 생성할 수 있게 되었습니다.

앞에 예제에서 A는 Generic Type으로 함수의 타입을 미리 정하지 않고 유연하게 받아들이기 위해서 지원하는 기능입니다.
A 뒤에 있는 * (asterisk mark)는 가변 인자를 나타내는 기호로, parameter가 A type의 인자를 개수에 상관없이 유연하게 받을 수 있도록 합니다. 다시 말하면, elems: A*는 A type의 인자들을 Seq[A]로 받습니다. A가 String type이라면 elems: String*는 String type의 인자들을 Seq[String]으로 받게 됩니다.
따라서, elems가 Seq이기 때문에 new 키워드를 사용해서 instance를 만드는 대신 elems.toList로 Seq를 List로 변환해 주었습니다.

정리하면, apply method의 핵심은 apply method를 생략하고 object의 이름을 함수처럼 사용할 수 있는 것입니다.
객체를 Instance 화 하는 것 뿐만 아니라, apply method에 어떤 기능을 하도록 코드를 작성했느냐에 따라서 개발자가 원하는 기능을 object 이름에 부여할 수 있게 됩니다.

핵심만 담기에는 너무 이야기할 게 많네요. 이번 글은 여기서 마무리하고 다음 글에서 스칼라의 객체 지향에 대해서 계속 이야기하겠습니다.

감사합니다.

comments 0