article thumbnail image
Published 2022. 2. 14. 01:22
728x90


📌  상속이란?

상속(Inheritance) 은 한 타입을 그대로 사용하면서 구현을 추가할 수 있도록 해주는 방법을 제공한다. 상속 받은 하위 클래스는 필요에 따라 상위 클래스에 정의된 메서드를 새롭게 구현할 수 있다. 이를 재정의(overriding)라 한다. 메서드를 재정의하면, 해당 메서드를 실행할 때 상위 타입의 메서드가 아닌 하위 타입에서 재정의한 메서드가 실행된다.

 

 

상속의 장점

  • 기존에 작성된 클래스를 재활용 할 수 있다.
  • 자식 클래스 설계 시 중복되는 멤버를 미리 부모 클래스에 작성해 놓으면, 자식 클래스에서는 해당 멤버를 작성하지 않아도 된다.
  • 클래스 간의 계층적 관계를 구성함으로써 다형성의 문법적 토대를 마련한다. 

 

 

 

상속의 종류

1. 인터페이스 상속

  • 다중 상속을 지원하지 않는 언어에서 다형성을 구현하는 방법
  • 순수하게 타입만을 상속받는 상속
  • 인터페이스에서 정의만한 오퍼레이션을 직접 구현하는 방식으로 상속
  • 추상화를 구현하기 위한 핵심

2. 구현 상속 (클래스 상속)

  • 상위 클래스에 정의된 기능을 재사용하기 위한 목적 (재사용성이 핵심 목적)
  • 재사용성 + 다형성의 기능을 제공

 

 

코틀린 상속의 예시

open class Beginner(val name: String) {
    open fun attack() {
        println("$name 가 기본 공격을 수행합니다.")
    }
}

class Wizard(name: String) : Beginner(name) {
    override fun attack() {
        super.attack() // super 키워드를 통해서 부모클래스의 attack 메서드 수행
        println("$name 가 마법 공격을 수행합니다.")
    }
}

fun main() {
    val wizard = Wizard("stark")
    wizard.attack()
}

// 실행 결과
// stark 가 기본 공격을 수행합니다.
// stark 가 마법 공격을 수행합니다.

 

 

 

 

 

 

 

 

📌  추상클래스 vs 인터페이스

1. 추상클래스

  • abstract 메서드를 가지고 있는 클래스
  • 코틀린의 경우 abstract 사용 시 클래스나 메서드에 open 키워드를 따로 명시하지 않아도 된다.
  • 자식 클래스는 추상 메서드를 반드시 구현해야 한다.
  • 생성자를 가질 수 있다.
  • 자바와 마찬가지로 코틀린에서도 클래스 다중 상속은 불가하다.

2. 인터페이스

  • 일반적으로 모든 메서드가 abstract인 클래스를 의미하지만, java 8 부터는 default 키워드를 통해 메소드 구현이 가능하다.
// java8 부터 default 키워드를 통해 메서드 구현 가능
public interface Animal {
  void eat(); 
  default void introduce() { 
  Systemp.out.println("안녕하세요");
  } 
}
  • 생성자는 만들 수 없다.
  • 인터페이스는 다중 상속이 가능하다.
  • 코틀린에서는  인터페이스에 프로퍼티, 추상 메서드, 일반 메서드를 모두 가질 수 있다.
interface Runner { 
    fun run() 
} 

interface Eater { 
    fun eat() { 
        println ("음식을 먹습니다") // 인테페이스에 메서드를 구현할 때 따로 키워드 필요 x
    } 
} 

class Dog : Runner, Eater { 
    override fun run() {
        println ("뜁니다")
    }
    override fun eat() {
        println ("사료를 먹습니다")
    } 
}

 

 

 

 

 

 

 

📌  다형성 및 추상화

다형성의 개념

다형성(Polymorphism) 은 한 객체가 여러 가지 모습을 갖는다는 것을 의미한다. 모습은 타입을 의미하며, 한 객체가 여러 타입을 가질 수 있는 것이다.

 

추상화의 개념

공통된 개념을 도출해서 추상 타입을 정의해준다. 또한 많은 책임을 가진 객체로부터 책임을 분리하는 촉매제가 되기도 한다.

 

 

다형성

  • 한 객체가 여러 타입을 가질 수 있다는 것을 의미
  • 동일한 요청(메시지)에 대해 서로 다른 방식으로 응답할 수 있는 능력

 

추상화

  • 객체 내부의 구현을 변경할 수 있는 유연함을 제공하는 또 다른 방법
  • 데이터나 프로세스 등을 의미가 비슷한 개념이나 표현으로 정의하는 방법
  • 구체적인 사물들 간의 공통점을 취하고 차이점을 버리는 일반화를 사용하거나, 중요한 부분을 강조하기 위해 불필요한 세부 사항을 제거함으로써 단순하게 만든다.

예를 들어, for, while, do while 등과 같은 문법은 반복한다는 구문을 추상화한 것이다. 실제로는 CPU의 명령을 통해서 반복이 구현되겠지만, 이 구현으로부터 반복이라는 개념을 뽑아내서 for, while, do~while 등으로 추상화한 것이라고 할 수 있다.  

 

 

 

 

코틀린 추상화 예제

추상화는 일반화를 통해서 구현될 수 있다. 일반화는 구체적인 사물들 간의 공통점을 취하고 차이점을 버리는 것이다.

 

 

예를 들어, 아래와 같이 SuperCar, SnowCar, FastCar 3개의 클래스가 있다고 가정하자.

class SuperCar {
    fun move() {
        println("슈퍼카가 달립니다.")
    }
}

class SnowCar {
    fun move() {
        println("눈에서 달립니다.")
    }
}

class WaterCar {
    fun move() {
        println("물 위에서 달립니다.")
    }
}

 

 

이들을 움직이게 하기 위해서는 아래와 같이 코드를 작성하게 된다.

 

 

fun main() {
    val superCar = SuperCar()
    val snowCar = SnowCar()
    val waterCar = WaterCar()
    
    superCar.move()
    snowCar.move()
    waterCar.move()
}

// 슈퍼카가 달립니다.
// 눈에서 달립니다.
// 물 위에서 달립니다.

 

 

 

여기서 움직인다는 행위는 같으므로 차의 공통된 속성인 'move'를 상위 클래스로 뽑아낼 수 있다. 또한 똑같은 자동차들이므로 Car라는 상위 클래스를 두어 상속 관계를 만들어도 무방하다.

 

따라서 다음과 같이 구조를 변경할 수 있다.

abstract class Car {
    abstract fun move()
}

class SuperCar : Car() {
    override fun move() {
        println("슈퍼카가 달립니다.")
    }
}

class SnowCar : Car() {
    override fun move() {
        println("눈에서 달립니다.")
    }
}

class WaterCar : Car() {
    override fun move() {
        println("물 위에서 달립니다.")
    }
}

 

 

그리고 하위 클래스들의 추상 메서드를 재정의 하면 된다. 이들을 움직이기 위한 코드는 아래와 같이 작성할 수 있다.

 

 

fun main() {
    val cars = arrayListOf<Car>(SuperCar(), SnowCar(), WaterCar())
    for (car in cars) {
        car.move()
    }
}

// 슈퍼카가 달립니다.
// 눈에서 달립니다.
// 물 위에서 달립니다.

 

결과적으로 우리는 일반화를 통해 추상화를 구현하자고 생각했고, 여기서 그 목적을 위한 방식은 바로 타입이다. 좀 더 상위 타입을 정의함으로써 추상화와 동시에 다형성을 구현할 수 있게 된 것이다.

 

 

 

 

 

📌  인터페이스에 대고 프로그래밍

객체 지향의 유명한 규칙 : 인터페이스에 대고 프로그래밍하기(program to interface)

  • 코틀린이나 자바 같은 언어는 자체적으로 인터페이스나 추상 클래스를 이용해서 개념적인 인터페이스를 제공하고 있다.
  • 인터페이스에 대고 프로그래밍하기(program to interface) 규칙은 추상화를 통한 유연함을 얻기 위한 규칙이다.
  • 인터페이스를 작성할 때에는 그 인터페이스를 사용하는 코드 입장에서 작성하자

🚨  주의할 점

  • 인터페이스를 사용해야 할 때는 변화 가능성이 높은 경우에 사용해야 한다.
  • 변경 가능성이 희박한 클래스에 대해 인터페이스를 만든다면 오히려 프로그램의 구조만 복잡해지고 유연함의 효과는 누릴 수 없는 상황이 발생할 수 있다.

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

'OOP' 카테고리의 다른 글

OOP - DI(Dependency Injection)와 서비스 로케이터  (0) 2023.02.26
OOP - SOLID 원칙  (0) 2022.10.27
OOP - 재사용: 상속보다 조립  (0) 2022.04.07
OOP - 객체 지향  (0) 2022.02.04
복사했습니다!