스칼라 객체의 Composition(구성)과 Inheritance(상속), Polymorphism(다형성)

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

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

스칼라의 객체 지향에 대해서 계속 이야기 하겠습니다.

2. Composition(구성)과 Inheritance(상속), Polymorphism(다형성)

객체를 구성하는 방법에는 Composition과 Inheritance가 있습니다.

  • Composition(구성)

Composition은 Class를 구성할 때 다른 객체를 가져와서 기능을 확장하거나 새로운 기능을 제공하는 방식입니다.

보통 new Keyword를 사용해서 만들어지는데요, Class 외부에서 객체를 만들어서 인수로 넘기거나 Class 내부에서 객체를 만들어서 사용합니다.

일반적으로 has-a 관계로 설명합니다.

class Engine(val horsepower: Int):
  def start(): Unit = println(s"Engine with $horsepower HP started.")

class Car(val brand: String, val engine: Engine):
  def startCar(): Unit =
    println(s"Starting car: $brand")
    engine.start()

val engine = new Engine(150)
val car = new Car("Toyota", engine)
  • Inheritance(상속)

Inheritance는 보통 Polymorphism과 함께 설명이 되는데요, Superclass (Parent class)의 속성과 Method를 Subclass (Child class)가 물려받고 확장하는 모습으로 구현됩니다.

Composition이 객체 간의 결합도가 낮아서 개별 객체에 대한 수정이 용이하고, 객체를 독립적으로 재사용할 수 있으며, 테스트도 용이하다는 장점을 가지고 있다면, Inheritance는 Superclass에서 정의된 코드를 Subclass에서 그대로 사용할 수 있고, 공통된 기능에 대해서 중복 코드를 줄이면서 기존 코드의 수정 없이 Subclass에서 기능 확장이 용이하다는 장점이 있습니다.

Inheritance는 extends 키워드를 사용해서 상속을 구현하며, 일반적으로 is-a 관계로 설명합니다.

class Animal:
  def makeSound(): String = "Some sound"

class Dog extends Animal:
  override def makeSound(): String = s"Bark and ${super.makeSound()}"

class Cat extends Animal:
  override def makeSound(): String =s"Meow and ${super.makeSound()}"

위 예제에서 override 키워드가 사용되는 이유는 Method Overriding을 명시화해서 Superclass에서 해당 field나 method에 변경이 있을 경우 컴파일 오류를 통해 바로 확인할 수 있도록 하기 위함입니다.

Java와 같이 문법이 엄격하지 않아서 override를 명시하지 않을 경우에는 Superclass가 바뀌어도 해당 코드는 유효하기 때문에 이로 인해 Runtime 중 오류가 발생할 수 있습니다.

이런 부분들이 Scala 설계에 매우 꼼꼼하게 반영이 되어 있어서 소프트웨어 개발에 있어서 많은 오류들을 미연에 방지할 수 있습니다.

  • Polymorphism(다형성)

다형성은 일반적인 의미로 여러 형태를 가질 수 있는 속성을 의미합니다.

이를 객체 지향 프로그래밍 언어에서 구체적으로 정의하면, Method Overloading, Method Overriding, Subtyping을 통해 객체의 형태와 기능을 변경할 수 있는 특성을 말합니다.

Method Overloading은 동일한 이름의 Method가 동일한 기능을 가지고 있으나, 매개변수의 타입이나 개수를 다르게 정의하는 것을 말합니다.

// Method Overloading
class Calculator:
  def add(x: Int, y: Int): Int = x + y
  def add(x: Double, y: Double): Double = x + y

val calc = new Calculator()
println(calc.add(5,3))        // 8
println(calc.add(5.0, 3.0))        // 8.0

Method Overriding은 앞에서 설명한 바와 같이 부모 클래스에서 정의한 메소드를 자식 클래스에서 재정의하여 사용하는 것을 말합니다.

즉 선언부는 동일하고 기능만 변경됩니다.

// Method Overriding
class Animal:
  def sound(): String = "Some sound"

class Dog extends Animal:
  override def sound(): String = "Bark"

class Cat extends Animal:
  override def sound(): String = "Meow"

Subtyping은 부모 Class의 Type으로 자식 Class의 Subtype을 Instance로 사용할 수 있는 특성입니다.

Supertype을 통해, 구체적으로 기능이 정의된 각각의 Subtype들을 다채롭게 선택해서 활용할 수 있음을 의미합니다.

// Subtyping
class Animal:
  def sound(): String = "Some sound"

class Dog extends Animal:
  override def sound(): String = "Bark"

class Cat extends Animal:
  override def sound(): String = "Meow"

val a1: Animal = new Dog
val a2: Animal = new Cat

val animals: List[Animal] = List(a1, a2)
animals.foreach(animal => println(animal.sound()))
// 출력:
// Bark
// Meow

Subtyping에 대해, 동작 방식에 대한 관점에서 Dynamic binding으로 설명하고 있습니다.

Dynamic Binding이란 런타임 시점에 Method 호출을 결정하는 Mechanism으로 객체의 실제 Type에 따라 호출할 Method가 결정됩니다.

즉, Supertype의 변수에 Subtype의 객체를 할당하면 Run Time 시점에서 실제 Type을 확인하고, Supertype의 Method가 Subtype의 Method로

Overriding되어 실행됩니다. 이를 통해 프로그램의 확장성과 재사용성을 높일 수 있습니다.

지금까지 객체의 Composition, inheritance, Polymorphism에 대해서 알아 봤는데요, 이번 글에서 설명한 것은 Scala 언어의 특징이라기 보다는 기본적인 객체 지향 언어의 특성에 좀 더 가까운 내용이었던 것 같습니다.

이 다음에 꼭 소개해야 할 부분이 Scala의 다중 상속과 Trait 인데요, 다음 글에서 이어서 설명 드리겠습니다.

감사합니다.

comments 0