본문 바로가기

Note

[객사오] 커피 구매를 위한 프로그램 업그레이드하기

객체지향의 사실과 오해의 5장을 읽었고, 마찬가지로 '커피 구매' 프로그램에 적용해보려고 한다.

 

 

책을 읽는동안에 작성했던 코드에서 달라지는 부분이 어디일지 궁금해서 엉덩이 들썩들썩였다 ㅋㅋㅋㅋㅋ

이번 5장에서 중요한 부분은 객체의 외부/내부를 분명히 하는 것!

 


 

이전 포스팅

 

[객사오] 커피 구매를 위한 프로그램에 적용해보기

어렴풋이 알고만 있던 객체지향에 대해서 공부해보고자 유명한 '객체지향의 사실과 오해' 책을 구매하였다. 사실 유튜브에서 객체지향 관련해서 영상(링크)을 보게되었는데 중간까지보다가 개

w36495.tistory.com

 


인터페이스 수정

// 수정 전

interface Customer {
    var money: Int

    fun buyCoffee()
}

interface Cashier {
    val menus: Map<String, Int>

    fun showMenu()
    fun serveCoffee(menu: String, money: Int)
    fun changeCharge(money: Int, price: Int): Int
}

interface Barista {
    fun makeCoffee(menu: String): String
}

 

// 수정 후

interface Customer {
    fun buyCoffee()
}

interface Cashier {
    fun showMenu()
    fun serveCoffee(menu: String, money: Int)
}

interface Barista {
    fun makeCoffee(menu: String): String
}

 

무엇이 달라졌을꼬 ..?

인터페이스 내부에 있던 데이터들을 제거해주고, 메서드도 제거하였다.

 

1️⃣ Customer 의 money

심지어 var 로 선언되어 있어서 외부에서 수정이 가능했었다. (헉)

 

2️⃣ Cashier 의 menu

showMenu() 를 통해 메뉴를 보여주는 행동을 하기때문에 제거하였다.

 

그런데 showMenu() 가 없었다면, menu 변수를 그대로 가져가도 될 것 같다!

왜냐하면 Immutable 의 형태이고, val 로 선언되어있으니 외부에서 수정은 불가능할뿐더러 메뉴를 보여주는 showMenu() 의 역할을 할 수 있기때문이라 생각한다.

 

3️⃣ Cashier 의 changeCharge()

하단 캐셔부분에 제거한 이유에 대해 작성하였다!


 

구현 클래스 수정

수정 전/후를 비교하다보니 코드가 길어지는 것 같아 손님, 캐셔 그리고 바리스타 따로따로 작성해보았다.

Customer

// 수정 전

class ACustomer : Customer {
    override var money: Int = 10_000
    private val cashier = ACasher()

    override fun buyCoffee() {
        cashier.showMenu()
        cashier.serveCoffee("아이스 아메리카노", money)
    }
}

// 수정 후

class ACustomer(
    private val cashier: Cashier
) : Customer {
    private var money: Int = 10_000

    override fun buyCoffee() {
        cashier.showMenu()
        selectMenu()
    }

    private fun selectMenu() {
        cashier.serveCoffee("아이스 아메리카노", money)
    }
}

 

 

1️⃣ Cashier 접근 방식 변경 (구현 클래스 -> 인터페이스)

ACashier 가 아닌 다른 캐셔로 대입이되어도 ACustomer 에는 영향이 없게되었다!

 

2️⃣ 메뉴를 선택하는 부분을 private 메서드로 변경

외부에서 접근하는 메서드가 아니기때문에 인터페이스로 뺄 필요가 없었으며, Customer 마다 선택하는 메뉴가 다를 것이기 때문에 내부 메서드로 작성해주었다.

 

Cashier

// 수정 전

class ACasher : Cashier {
    private val barista = ABarista()
    override val menus: Map<String, Int>
        get() = mapOf("에스프레소" to 3_000, "아이스 아메리카노" to 4_500, "카페라떼" to 5_500, "카라멜 마키아또" to 6_000)

    override fun showMenu() {
        println("==== M E N U ====")

        menus.forEach { (menu, price) ->
            println("$menu ---> $price 원")
        }
    }

    override fun serveCoffee(menu: String, money: Int) {
        val change = changeCharge(money, menus.getValue(menu))
        println("거스름돈은 $change 원 입니다.")

        val completeMenu = barista.makeCoffee(menu)
        println("주문하신 $completeMenu 나왔습니다.")
    }

    override fun changeCharge(money: Int, price: Int): Int {
        return if (money > price) return money - price else 0
    }
}

// 수정 후

class ACasher(
    private val barista: Barista
) : Cashier {
    private val menus: Map<String, Int>
        get() = mapOf("에스프레소" to 3_000, "아이스 아메리카노" to 4_500, "카페라떼" to 5_500, "카라멜 마키아또" to 6_000)

    override fun showMenu() {
        println("==== M E N U ====")

        menus.forEach { (menu, price) ->
            println("$menu ---> $price 원")
        }
    }

    override fun serveCoffee(menu: String, money: Int) {
        val change = changeCharge(money, menus.getValue(menu))
        println("거스름돈은 $change 원 입니다.")

        val completeMenu = barista.makeCoffee(menu)
        println("주문하신 $completeMenu 나왔습니다.")
    }

    private fun changeCharge(money: Int, price: Int): Int {
        return if (money > price) return money - price else 0
    }
}

 

캐셔또한 Customer 처럼 변경해주었다.

 

1️⃣ barista 접근 방법 (구현 클래스 -> 인터페이스)

 

2️⃣ changeCarge() 를 private 로 선언

이 부분은 고민이 있었다 .. ..

지금은 주문을 하면 자동으로 거스름돈을 주게끔 작성했기때문에 따로 인터페이스로 빼내야할까? 에 의문을 가졌다.

그런데 외부에서 changeCarge() 를 요청하는 것이 아닌, Cashier 내부에서 메서드를 사용하고 있기 때문에 private 메서드로 수정해주었다.

 

추후에 다양한 메시지들이 생겨나고 객체들이 만들어진다면 주문을 담당하는 인터페이스가 존재한다면, 그 안에 위치할 것 같다!

 

바리스타의 코드는 변경된 부분이 없어서 기재하지 않았다!


외부 객체는 공용 인터페이스에만 의존해야 하고, 구현 세부 사항에 대해서는 직접적으로 의존해서는 안된다.
객체지향의 사실과 오해 (p172)

 

이번에 수정하는데 많은 도움이 되었던 문장을 끝으로 이번 포스팅도 끝!

 

아차차! 호출하는 부분도 많이 달라졌다.

 

// 수정 전

fun main() {
    ACustomer().buyCoffee()
}

// 수정 후

fun main() {
    val barista = ABarista()
    val cashier = ACasher(barista)
    val customer = ACustomer(cashier)

    customer.buyCoffee()
}

 

 

외부에서 각 객체의 인스턴스를 생성하여 생성자로 주입해주었다.

헉 의존성 역전 .... ...?