본문 바로가기
Engineering/Kotlin

Constructor

by 쿨쥰 2023. 5. 16.

Constructor

개요

일반적으로 객체 지향 언어에서 사용하는 생성자는 클래스 내에서 객체를 생성하고 초기화 하기 위한 특별한 메서드를 의미한다.

객체의 기본 상태를 설정하는 공간이며, 일반적인 함수와는 다른 방식으로 클래스 객체가 인자를 전달받도록 한다.

즉, 인스턴스가 생성 될 때 디폴트로 호출되는 ‘인스턴스 초기화 메서드’ 라고 이해하면 쉽다.

따라서 인스턴스 변수의 초기화 작업에 주로 사용되며, 인스턴스 생성 시에 실행 되어야 할 루틴을 위해서도 사용 되기도 한다.

 

Kotlin 의 Primary constructor

Kotlin 의 클래스는 하나의 Primary constructor와 복수의 Secondary constructor를 가질 수 있다.

Primary constructor는 constructor를 생략해서 주로 사용 되어, 사용 형태는 아래와 같게 된다.

class Person constructor(age: Int, name: String) {
  // todo
}

class Person(age: Int, name: String) {
    //todo
}

여기서 초기 값을 세팅 해야 할때는 아래와 같이 초기화 코드를 init 블럭 안에 작성 한다.

다시 말해, Primary constructor의 파라미터는 프로퍼티의 초기화와 init 블럭에서 사용될 수 있다.

class Person constructor(ageParam: Int, nameParam: String) {
    private val nameLengh = nameParam.length
    private var age = ageParam
    private var name = nameParam
    init {
        println("${name}님은 ${age}세 이고 이름의 길이는 ${nameLengh} 이다" )
    }
}

위 코드는 아래와 같이 Primary constructor 내에서 프로퍼티 선언과 초기화를 한번에 하는 방식으로 다시 쓸 수 도 있다.

class Person2 constructor(var age: Int, var name: String) {
    private val nameLengh = name.length

    init {
        age = 100
        println("${name}님은 ${age}세 이고 이름의 길이는 ${nameLengh} 이다" )
    }
}


fun main() {
    val person2 = Person(40,"Jordan")
}

변수는 Primary Constructor 안에서 초기화 되었고, init 블럭 안에서 age 가 100으로 초기실행 되었으므로

클라이언트에서 40세를 담아 호출하더라도 100세가 출력 된다.

 

 

Kotlin 의 Secondary constructor

클래스 안에서 secondary constructor 를 여러개 줄 수 도 있다.

이는 하나의 클래스 안에서 여러 형태의 구조를 제공할때 쓰일 수 있는 패턴이다.

class Person2 {
    init {
        println("init 실행")
    }
    constructor(name: String, age: Int)  {
        //todo
    }
    constructor(name: String, age: Int, gender: String)  {
        //todo
    }
}


fun main() {
    val person2 = Person2("Jordan", 40, "Male")
}

init 블럭은 Primary constructor 의 영역으로 간주되므로 Secondary constructor 실행 이전에 프로퍼티 초기화 코드와 함께 init 블럭이 먼저 실행 된다.

위 예제에서는 Primary constructor 가 생략되었더라도, 암시적으로 위임되기 때문에 초기화 블럭은 항상 Secondary constructor 보다 항상 먼저 실행 되는 셈이다.

 

 

사용 예시

숙박, 항공권 등의 여가 서비스를 제공하는 예약 시스템을 가정해서, 핸들러 형태로 서비스 인터랙션을 구현 한다.

 

@Service
class CommerceHandler(
    private val lodgingClient: ServiceClient,
    private val flightClient: ServiceClient,
) {

    fun getBooking(orderSheet: OrderSheet): ServiceBooking {
        val productCode = orderSheet.productCode
        val serviceClient = getClient(productCode)
        return when (productCode) {
            ProductCode.LODGING -> LodgingBooking(serviceClient, orderSheet)
            ProductCode.FLIGHT -> FlightBooking(serviceClient, orderSheet)
        }
    }

    fun getBooking(orderSheet: OrderSheet, bookings: List<Booking>): ServiceBooking {
        val productCode = orderSheet.productCode
        val serviceClient = getClient(productCode)
        return when (productCode) {
            ProductCode.LODGING -> LodgingBooking(serviceClient, orderSheet, bookings)
            ProductCode.FLIGHT -> FlightBooking(serviceClient, orderSheet, bookings)
        }
    }

< ServiceHandler - 서비스 핸들러 >

 

 

각 서비스를 관장하는 도메인 시스템은 MSA 형태로 분산되어 있다고 가정하여, Lodging, Flight 시스템의 클라이언트를 각각 선언했다.

이들은 SeviceClient 라는 공통된 형태를 가지고 있다.

getBooking 함수 예약을 생성하는 함수인데 이는 상황에 따라

  • 주문장을 이용하여 직접 예약 생성을 하고 리턴하는 경우
  • 주문장 + 예약된 리스트까지 함께 받아서 이미 예약된 내역 상세를 리턴하는 경우

각각의 용도에 맞게끔 인풋 파라미터를 각각 다르게 구성하여 함수의 다형성을 보장했다.

이 getBooking 함수안에서 product code 가 LODGING 이라 가정하고 이때 숙박 예약을 관장하는 LodgingBooking 이라는 예약 생성 클래스를 호출한다.

 

class LodgingBooking : ServiceBooking {

    private val serviceClient: ServiceClient
    override val bookings: List<Booking>
    override val bookingAmount: Long
    override val orderSheet: OrderSheet
    

    constructor(serviceClient: ServiceClient, orderSheet: OrderSheet) {
        val bookings = BookingMaker(orderSheet).make()
        this.bookings = bookings
        this.orderSheet = orderSheet
        this.bookingAmount = bookings.sumOf { it.getTotalPrice() }
        this.serviceClient = serviceClient
    }

    constructor(serviceClient: ServiceClient, orderSheet: OrderSheet, bookings: List<Booking>) {
        this.bookings = bookings
        this.orderSheet = orderSheet
        this.amountOfPay = bookings.sumOf { it.getTotalPrice() }
        this.serviceClient = serviceClient
    }

    override fun book(orderInteraction: OrderInteraction): BoookingResult {
        val bookingResult =  .....
        return bookingResult
    }

    override fun revert() {
        for (booking in bookings) {
            booking.revert()
        }
                  ....
    }

    override fun cancel(cause: String) {
        for (booking in bookings) {
            booking.cancel()
        }
                  .....
    }
}

< LodgingBooking.kt - 숙박 예약 생성 클래스 >

 

Secondary constructor 를 두개 생성해서, 예약 정보를 초기 세팅 한다.

첫번째 constructor 는 주문장을 이용해 직접 예약까지 만드는 use case 를 커버하므로, 파라미터로 받은 주문장을 가지고 직접 예약을 만든 뒤 booking 변수에 생성된 결과를 담는다.

두번째 constructor 는 주문장과 이미 예약된 예약 리스트를 받으므로, booking 변수에는 파라미터로 받은 booking 내용을 그대로 담는 식으로 초기화 한다.

 

 


JAVA 에 비해 constructor 사용에 있어 kotlin이 훨씬 느슨하고 자유롭게 쓸 수 있다.

'Engineering > Kotlin' 카테고리의 다른 글

Companion object  (0) 2023.05.18
Interface VS Abstract?  (0) 2023.05.14

댓글