Spring 은 IoC container 에 bean 객체를 생성한 후 그 객체를 등록한 후, 필요할 때 사용한다. Interface 는 객체를 생성할 수 없기 때문에 bean 이 될 수 없다.

그렇다면 Abstract Class 는 어떨까? 아래 코드를 보면 일단 bean 으로 등록할 수 없다.

@RestController
class Controller(
    val abstractService: AbstractService,
) {
    @GetMapping("/abstract")
    fun getAbstract() = abstractService.abstractFunction()
}

@Service
abstract class AbstractService {
    fun abstractFunction() = ""
}

아래와 같은 메시지가 뜨면서 애플리케이션이 멈춘다.

***************************
APPLICATION FAILED TO START
***************************

Description:

Parameter 0 of constructor in com.demo.controller.Controller required a bean of type 'com.demo.service.AbstractService' that could not be found.

왜 그럴까? 필자에게 하나의 가설이 있었다. Abstract Class 의 메서드 역시 abstract method 다. Child Class 가 어떻게 구현하느냐에 따라 달라지기 때문에 Abstract Class 는 bean 으로 등록할 수 없다. 그렇다면, Abstract Class 의 모든 메서드가 final method 라면 bean 으로 등록할 수 있을까? 그래서 아래와 같이 구현을 한 후 애플리케이션을 run 했다.

@Service
abstract class AbstractService {
    final fun abstractFunction() = ""
    final override fun equals(other: Any?) = false
    final override fun hashCode() = 1
    final override fun toString() = ""
}

하지만 결과는 똑같았다.

***************************
APPLICATION FAILED TO START
***************************

Description:

Parameter 0 of constructor in com.demo.controller.Controller required a bean of type 'com.demo.service.AbstractService' that could not be found.

다 삽질이었다. Java/Kotlin 의 Abstract Class 는 instance 를 만들 수 없다. 그렇기 때문에 bean 이 될 수 없다.

이제는 좀 다른 얘기를 해보자. 언제 Abstract Class 를 사용하고 언제 Interface 를 사용해야 할까? Abstract Class 와 Interface 의 가장 큰 차이점은 final 을 담을 수 있는지 여부다. Abstract Class 는 final keyword 를 사용할 수 있는 반면, Interface 는 final keyword 를 사용할 수 없다. 참고로 Kotlin Abstract Class 의 모든 멤버는 final 이니, final 이 아닌 멤버에만 abstract keyword 를 붙인다.

Abstract Class 가 Interface 와 달리 final keyword 를 사용할 수 있는점은 encapsulation 을 사용할 때 유용하다.

abstract class AbstractClazz(
    val a: String = "에이다"
) {
    abstract fun abstractFunction(): String
    fun finalFunction() = "final"
}

class ConcreteClazz : AbstractClazz() {
    override fun abstractFunction() = ""
}
fun main(args: Array<String>) {
    runApplication<DemoApplication>(*args)

    println("ㅋㅋㅋㅋㅋㅋㅋㅋ")
    println(ConcreteClazz().a)
    println(ConcreteClazz().finalFunction())
}
2022-03-16 01:16:19.911  INFO 2537 --- [           main] com.demo.DemoApplicationKt   : Started CryptoApplicationKt in 0.703 seconds (JVM running for 0.956)
ㅋㅋㅋㅋㅋㅋㅋㅋ
에이다
final

위의 코드와 콘솔에서 확인할 수 있듯이, Abstract Class 에서 final 로 지정된 멤버는 child Class 에서 override 할 수 없는 동시에, child Class 가 해당 멤버를 호출할 수 있다.

필자가 생각하기에 Abstract Class 는 Template Method Pattern 에서 사용될 수 있을 것 같다.

Reference