본문 바로가기

Study/Kotlin

Kotlin / 추상클래스, 인터페이스 알아보기

반응형

1. 추상클래스

추상클래스는 abstract 예약어로 지정한 클래스이다.

직접 객체를 생성할 수 없고, 항상 다른 클래스에서 상속해서 추상 메서드를 구현해야 한다.

abstract class Person { // 추상 클래스 정의
    abstract val name: String // 추상 속성 정의
    abstract fun displayName() // 추상 메서드 정의
}

class Woman : Person() { // 구현 클래스 정의
    override val name: String = "은정" // 추상 속성 오버라이딩
    override fun displayName() { // 추상 메서드 오버라이딩
        println("이름 : $name")
    }
}

val woman = Woman()
woman.displayName()

// 이름 : 은정

 

추상 클래스 내의 일반 속성과 일반 메서드 정의

abstract class Person2 {
    var age: Int = 20 // 일반 속성 정의
    abstract val name: String

    open fun displaySSN(ssn: Int) { // 일반 메서드 정의
        println("주민번호 : $ssn")
    }

    abstract fun displayName()
}

class Woman : Person2() {
    override val name: String = "은정"
    override fun displayName() {
        println("이름 : $name")
    }

    override fun displaySSN(ssn: Int) { // 일반 메서드 오버라이딩
        super.displaySSN(ssn) // 추상 클래스의 일반메서드 호출
    }
}

val woman = Woman()
woman.displayName()
println(woman.age)
woman.displaySSN(1234)

// 이름 : 은정
// 20
// displaySSN
// 주민번호 : 1234

추상속성과 메서드는 반드시 구현 클래스에서 구현해야 하지만, 일반 속성과 메서드는 상속해서 재정의하려면 반드시 open 지시자 정의가 필요하다.

  • 추상 클래스 내의 일반 속성과 메서드는 초깃값과 실제 함수의 코드 블록을 가지고 있어야 한다.
  • 일반 메서드를 정의하고 open을 처리해서 상속받는 클래스에서 이 메서드를 재정의할 수 있다.

 

추상클래스 활용

추상 클래스를 정의하면 여러 클래스에 동일한 규칙을 제한할 수 있는 공통 기능을 제공한다.

object 표현식 등에서 추상 클래스를 상속받아 구현할 수 있다.

abstract class BiOperator{
    abstract fun cal(x: Int, y: Int):Int
}

class Add: BiOperator(){
    override fun cal(x: Int, y: Int) = x + y
}
class Sub: BiOperator(){
    override fun cal(x: Int, y: Int) = x - y
}

fun cal(obj: BiOperator,x:Int,y:Int) = obj.cal(x,y)

val add : BiOperator = Add() // 객체 생성
val x1 = cal(add,10,20)
println("덧셈 : $x1") // 덧셈 : 30

val sub : BiOperator = Sub()
val x2 = cal(sub,10,20)
println("뺄셈 : $x2") // 뺄셈 : -10

추상 클래스의 추상 메서드를 다양한 하위 클래스의 표준 메서드로 활용될 수 있다.

객체를 만들어 함수에 전달하면 각각의 객체는 동일한 메서드가 호출되어 결괏값을 반환한다.

이런 방식을 사용하면 추상 클래스의 메서드가 구현 클래스를 제어해서 처리하는 것을 알 수 있다.

 

추상 클래스를 익명 클래스에서 상속받아 처리하기

추상 클래스를 정의하고 이를 object 표현식에서 상속받아 익명 객체를 만들 수 있다.

abstract class Weapon{
    fun move(){
        println("이동합니다.")
    }
    abstract fun attack()
}
var w2 = object: Weapon(){ // 객체 표현식으로 익명 객체
    override fun attack() { // 추상 메서드 구현
        println("공중 공격을 해요")
    }
}

w2.move() // 이동합니다.
w2.attack() // 공중 공격을 해요

추상 클래스를 정의하고, 일반 메서드와 추상 메서드를 가진다.

변수에 할당하는 객체를 만들고 이때는 object 표현식에 추상 클래스를 상속해서 객체를 만든다.

이 객체는 추상 클래스를 상속해 추상 메서드를 구현해서 2개의 메서드를 모두 실행할 수 있다.

 

추상 클래스 내에 추상 확장함수 처리

abstract class Base1 {
    abstract fun String.extension(x: String): String
}

class Derived1 : Base1() {
    override fun String.extension(x: String): String {
        return "$this $x !!!"
    }

    fun callExtension(c: String): String {
        return "hello".extension(c)
    }
}

val base = Derived1()
println(base.callExtension("코틀린"))

fun Base1.selectPrint() = println("추상클래스의 확장함수")
base.selectPrint()

// hello 코틀린 !!!
// 추상클래스의 확장함수

추상 클래스 Base1를 정의했다.

구현 클래스 Derived1에서 추상 확장함수를 구현하고 일반 메서드에서 이 확장함수를 호출해 반환값을 처리했다.

추상 클래스로 확장함수를 정의하면 이 추상클래스를 상속하는 클래스는 확장함수를 메서드처럼 사용이 가능하다.

 

2. 인터페이스

클래스와 인터페이스 차이

  • 클래스는 객체를 생성해서 내부의 속성과 메서드를 사용한다.
  • 인터페이스는 추상 속성과 추상 메서드를 기본으로 처리한다. 하지만, 추상 클래스처럼 객체를 생성할 수 없다.

상속과 구현 차이

  • 상속은 추상 클래스나 일반 클래스 간의 관계를 구성하는 것이다. 
  • 구현은 클래스가 인터페이스 내의 추상 멤버를 일반 멤버로 작성하는 것이다. 

인터페이스 정의 알아보기

interface 예약어를 사용한다.

인터페이스는 속성과 메서드 선언(실제 구현이 없는 상태를 작성)만 한다. 

실제 구현은 이 인터페이스를 상속한 클래스에서 작성한다.

interface Clickable { // 인터페이스 정의
    fun up(): Unit // 추상 메서드
    fun down(): Unit
}

class TvVolumn : Clickable { // 인터페이스 상속
    override fun up() { // 추상메서드 구현
        println("tv 볼륨을 올려요")
    }

    override fun down() {
        println("tv 볼륨을 내려요")
    }
}

val vol = TvVolumn() // 객체 생성
vol.up() // 메서드 실행
vol.down()

// tv 볼륨을 올려요
// tv 볼륨을 내려요

 

여러 인터페이스 상속 구현

여러 개의 인터페이스를 상속할 수 있다. 

인터페이스에 있는 추상 속성과 추상 메서드를 구현 클래스 내에 모두 구현해야 한다.

interface Aable { // 인터페이스 정의
    fun callMe() { // 일반 메서드
        println("인터페이스 Aable")
    }
}

interface Bable {
    fun callMe() {
        println("인터페이스 Bable")
    }
}

class Child : Aable, Bable { // 인터페이스 상속
    override fun callMe() { // 일반 메서드 재정의
        super<Aable>.callMe() // super를 사용해 Aable 호출
        super<Bable>.callMe() // super를 사용해 Bable 호출
    }
}

val obj = Child()
obj.callMe()

// 인터페이스 Aable
// 인터페이스 Bable

 

인터페이스 계층 처리

인터페이스도 다른 인터페이스를 상속할 수 있다. -> 인터페이스를 계층으로 만들어서 사용할 수 있다.

계층을 만드는 이유는 구현 클래스가 다양할 경우 구현 클래스의 상속을 계층에 맞춰서 만들 수 있기 때문이다.

보통 컬렉션 패키지를 보면, 대부분 인터페이스로 계층을 만들고 필요한 경우 구현 클래스가 상속받아서 인터페이스 범위에 따라 작동하도록 구성한다.

 

interface Aable1 { // 최상위 인터페이스 정의
    fun absMethod(): Unit
}

interface Bable1 {
    fun bMethod(): Unit
}

interface Cable : Aable1, Bable1 { // 인터페이스 상속
    override abstract fun absMethod(): Unit // 상속한 인터페이스 재정의 --> 잘 사용 X
    fun method(): Unit
}

class ABablity : Cable {
    override fun absMethod() = println("야호 !!!")
    override fun bMethod() = println("낙성대")
    override fun method() = println("관악산")
}

val a: Aable1 = ABablity()
// a.method() // 제공하지 않는 메서드 호출시 에러
a.absMethod()

val b: Bable1 = ABablity()
b.bMethod()

val c: Cable = ABablity()
c.bMethod()

// 야호 !!!
// 낙성대
// 낙성대

변수의 자료형에 3개의 인터페이스를 정의하면 자기 인터페이스에 구현된 메서드만 실행할 수 있다.

이처럼 다양한 인터페이스를 상속해서 클래스를 만들어도 특정 인터페이스로 자료형을 제한하면 내부의 모든 메서드를 사용할 수 있는 것이 아니다.

 

3. 봉인(Sealed) 클래스

Sealed 클래스는 특정 추상 클래스를 상속해 구현하는 것을 제한하기 위해 별도로 지정해 사용한다.

특정 파일이나 Sealed 클래스 내부에 한정해서 작성해 클래스의 상속관계를 한정한다.

 

Sealed 클래스를 상속하는 서브클래스는 반드시 같은 파일 내에 선언해야 한다.

서브클래스는 class, data class, object 모두 가능하다.

 

sealed class SealedClass { // 봉인 클래스 정의
    class SubX(val intVal: Int) : SealedClass() // 내부 클래스 정의
    class SubY(val stringVal: String) : SealedClass()
}

class SubZ(val longVal: Long) : SealedClass() // 외부 클래스 정의

fun printlnType(type: SealedClass): String =
    when (type) {
        is SealedClass.SubX -> "매개변수 타입 : integer"
        is SealedClass.SubY -> "매개변수 타입 : string"
        is SubZ -> "매개변수 타입 : long"
    } // else 필요 없음
    
    
println(printlnType(SubZ(100)))
println(printlnType(SealedClass.SubY("문자열")))
println(printlnType(SealedClass.SubX(100)))

// 매개변수 타입 : long
// 매개변수 타입 : string
// 매개변수 타입 : integer

 

Sealed 클래스는 명확히 하위 클래스를 알 수 있어서 when 표현식에서 만들어진 객체가 어떤 하위 클래스인지 점검할 수 있다.

이때 현재 상태로 모든 하위 클래스를 명확히 알 수 있어서 별도의 else가 필요 없다.

 

+++)

Sealed클래스는 기본적으로 추상클래스고, Sealed 인터페이스도 추가로 생겼다.

특정 코틀린 버전 이상부터는 굳이 같은 파일 안에 없어도 되고, 같은 패키지 안에만 있으면 된다.

보통은 같은 파일 안에 넣긴 한다.

Sealed클래스 : 범위를 제한한다. 무분별하게 상속하는걸 막기 위해!

Sealed클래스가 상속을 제한하면서 얻는 이점 : when을 쓸때 inner class를 쓰면 else를 쓰지 않아도 된다. sealed 클래스도 마찬가지! 이미 제한된 클래스이기 때문

 

인터페이스에서 프로퍼티 ? 

코틀린의 프로퍼티는 게터만 있는건지, 게터와 세터를 쓸지 결정하면 되는 거다. .

추상클래스 : activity 구현체들의 추상적인 개념이다. (실제로 존재하지 않지만,, ex BaseActivity)