IMCAT.DEV
로그인 가입
Kotlin 입문 객체지향

Kotlin 인터페이스와 추상 클래스

Kotlin 인터페이스와 추상 클래스 — 초급

인터페이스와 추상 클래스

1. 도입 (왜 배워야 하는가)

Kotlin을 비롯한 객체지향 프로그래밍 언어에서 인터페이스와 추상 클래스는 매우 중요한 개념입니다. 이 두 가지를 이해하는 것은 소프트웨어의 구조를 설계하고 유지보수하기 쉽게 해줍니다. 실무에서는 다양한 객체들이 어떻게 상호작용하는지를 정의하는 데에 필수적입니다.

인터페이스와 추상 클래스를 모르면 코드의 재사용성과 확장성이 떨어질 수 있습니다. 예를 들어, 여러 종류의 동물 클래스를 만들 때 각각의 동물이 공통적으로 지녀야 할 속성이나 행동을 정의하는 데 이 두 개념이 도움을 줍니다. 이를 통해 코드의 중복을 줄이고, 나중에 새로운 동물 클래스를 추가할 때도 쉽게 할 수 있습니다.

2. 핵심 개념 설명

  • 인터페이스: 특정 클래스가 반드시 구현해야 하는 메서드의 집합입니다. 인터페이스는 특정 행동을 정의하며, 여러 클래스가 이를 구현할 수 있습니다. 예를 들어, "운전할 수 있는" 차량 인터페이스를 만들면 자동차, 오토바이, 트럭 등 다양한 차량 클래스가 이를 구현할 수 있습니다.
  • 추상 클래스: 일반적인 속성 및 메서드를 가질 수 있는 클래스입니다. 직접 인스턴스를 생성할 수 없지만, 이를 상속받은 서브클래스에서 구체화하여 사용할 수 있습니다. 예를 들어, "동물"이라는 추상 클래스를 만들고, 이를 상속받아 "개"와 "고양이" 클래스를 구현할 수 있습니다.
  • 용어정의
    인터페이스특정 메서드의 집합으로, 구현해야 할 행동을 정의
    추상 클래스일반적인 속성과 메서드를 가진, 인스턴스화 할 수 없는 클래스

    3. 기본 코드 예제

    인터페이스 예제

    interface Drivable {
        fun drive()
    }
    
    class Car : Drivable {
        override fun drive() {
            println("자동차가 주행합니다.")
        }
    }
    
    // 사용 예
    fun main() {
        val myCar = Car()
        myCar.drive()
    }
  • 실행 결과: 자동차가 주행합니다.
  • 코드 설명:
  • - interface Drivable: 드라이브 행동을 정의하는 인터페이스를 생성합니다. - class Car : Drivable: Car 클래스가 Drivable 인터페이스를 구현합니다. - override fun drive(): 인터페이스의 메서드를 구현합니다. - myCar.drive(): Car 객체를 생성하고 드라이브 메서드를 호출하여 "자동차가 주행합니다." 출력합니다.

    추상 클래스 예제

    abstract class Animal {
        abstract fun makeSound()
    }
    
    class Dog : Animal() {
        override fun makeSound() {
            println("멍멍!")
        }
    }
    
    // 사용 예
    fun main() {
        val myDog = Dog()
        myDog.makeSound()
    }
  • 실행 결과: 멍멍!
  • 코드 설명:
  • - abstract class Animal: 동물의 기본 속성을 정의하는 추상 클래스를 생성합니다. - abstract fun makeSound(): 서브클래스에서 구현해야 할 메서드를 정의합니다. - class Dog : Animal(): Dog 클래스가 Animal 클래스를 상속받습니다. - myDog.makeSound(): Dog 객체를 생성하고 makeSound 메서드를 호출하여 "멍멍!" 출력합니다.

    4. 실전 활용 예제

    예제 1: 쇼핑몰 장바구니 구현

    interface CartItem {
        fun getPrice(): Double
    }
    
    class Product(val name: String, val price: Double) : CartItem {
        override fun getPrice(): Double {
            return price
        }
    }
    
    class Cart {
        private val items = mutableListOf<CartItem>()
    
        fun addItem(item: CartItem) {
            items.add(item)
        }
    
        fun totalPrice(): Double {
            return items.sumOf { it.getPrice() }
        }
    }
    
    // 사용 예
    fun main() {
        val cart = Cart()
        cart.addItem(Product("책", 15000.0))
        cart.addItem(Product("펜", 5000.0))
        
        println("총 가격: ${cart.totalPrice()}원") // 총 가격: 20000.0원
    }
  • 상황 설명: 쇼핑몰의 장바구니에서 다양한 상품의 가격을 계산하는 상황입니다. CartItem 인터페이스는 상품의 가격을 가져오는 메서드를 정의하고, Product 클래스가 이를 구현합니다.
  • 예제 2: 게임 캐릭터 구현

    abstract class Character(val name: String) {
        abstract fun attack(): String
    }
    
    class Warrior(name: String) : Character(name) {
        override fun attack(): String {
            return "$name이(가) 공격합니다!"
        }
    }
    
    // 사용 예
    fun main() {
        val warrior = Warrior("전사")
        println(warrior.attack()) // 전사가(가) 공격합니다!
    }
  • 상황 설명: 게임에서 캐릭터의 공격 행동을 정의하는 예제입니다. Character 추상 클래스는 캐릭터의 기본 속성을 정의하고, Warrior 클래스가 이를 상속받아 공격 행동을 구현합니다.
  • 5. 흔한 실수와 주의사항

    실수 1: 인터페이스의 메서드를 구현하지 않기

    class Bike : Drivable {
        // fun drive()는 구현하지 않음
    }
  • 문제점: Drivable 인터페이스의 메서드를 구현하지 않으면 컴파일 오류가 발생합니다.
  • 실수 2: 추상 클래스의 추상 메서드를 구현하지 않기

    class Cat : Animal() {
        // fun makeSound()는 구현하지 않음
    }
  • 문제점: Animal 추상 클래스의 추상 메서드를 구현하지 않으면 컴파일 오류가 발생합니다.
  • 6. 정리

  • 인터페이스는 여러 클래스에서 공통적으로 구현해야 할 행동을 정의합니다.
  • 추상 클래스는 공통 속성과 메서드를 정의하고, 이를 상속받아 구체적인 클래스를 구현합니다.
  • 이 두 개념을 적절히 활용하면 코드의 재사용성과 유지보수성을 높일 수 있습니다.
  • 다음에 학습하면 좋은 주제

  • 다형성(Polymorphism): 같은 인터페이스나 추상 클래스를 통해 서로 다른 객체를 처리하는 방법
  • 상속(Inheritance): 클래스 간의 관계를 이해하고 활용하는 법
  • 코틀린 고급 기능: 확장 함수(Extension Functions)와 고차 함수(Higher-Order Functions)