Kotlin

[코틀린 코딩 습작] Visitor

[하마] 이승현 (wowlsh93@gmail.com) 2021. 5. 20. 14:35

상황은 다음과 같다.
어느 온라인서점에는 멤버별로 금,은,동의 등급이 있으며, 
각 멤버에게 등급별로 베네핏을 주려고 하며, 베네핏의 종류는 A,B,C가 있다.
멤버는 각 베네핏을 모두 합친 만큼의 베네핏을 받을 수 있다.

원래 베네핏에 대한 로직은 더 복잡해 질 수 있지만, 예제에서는 10,20,30점으로 굉장히 단순화 하였다.

code1)

enum class GRADE {
    BRONZE,
    SILVER,
    GOLD
}

class Member(val grade: GRADE) {

    fun calcBenefit(): Int {
        var total = 0
        when(grade) {
            GRADE.BRONZE -> {
                // discount benefit
                total += 10

                // coupon benefit
                total += 20

                // point benefit
                total += 30
            }
            GRADE.SILVER -> {
                // discount benefit
                total += 20

                // coupon benefit
                total += 30

                // point benefit
                total += 40
            }
            GRADE.GOLD -> {
                // a benefit
                total += 30

                // b benefit
                total += 40

                // c benefit
                total += 50
            }
            else -> {
                throw IllegalArgumentException("unknown grade type!!")
            }
        }
        return total
    }
}

fun main() {
    val memberA = Member(GRADE.BRONZE)
    val benefitA = memberA.calcBenefit()
    println("BRONZE Benefit: " + benefitA)

    val memberB = Member(GRADE.GOLD)
    val benefitB = memberB.calcBenefit()
    println("GOLD Benefit: " + benefitB)
}


code2)

sealed class Member
class BronzeMember : Member()
class SilverMember : Member()
class GoldMember : Member()

class Benefit {
    fun discount(member: Member): Int {
        return when(member){
            is BronzeMember -> 10
            is SilverMember -> 20
            is GoldMember -> 30
        }
    }

    fun coupon(member: Member): Int {
        return when(member){
            is BronzeMember -> 20
            is SilverMember -> 30
            is GoldMember -> 40
        }
    }

    fun point(member: Member): Int {
        return when(member){
            is BronzeMember -> 30
            is SilverMember -> 40
            is GoldMember -> 50
        }
    }
}


fun main() {

    val benefit = Benefit()
    val memberA = BronzeMember() 
    val totalBenefitA = benefit.discount(memberA) + benefit.coupon(memberA) + benefit.point(memberA)
    println("BRONZE Benefit: " + totalBenefitA)
}

code3) visitor 패턴  
보통 요소들을 iterator 돌면서 로직을 적용하는데, visitor는 그 반대로 생각하면 쉽다. 
로직이 요소들을 방문하면서 액션이 이루어진다. 이 코드에서 요소는 멤버이고, 로직은 각 베네핏종류이다. 


sealed class Member {
    var totalBenefit : Int = 0
    abstract  fun calcBenefit(benefit: BenefitVisitor)
}

class BronzeMember : Member() {
    override fun calcBenefit(benefit: BenefitVisitor) {
        totalBenefit += benefit.calcBenefit(this)
    }
}

class SilverMember : Member(){
    override fun calcBenefit(benefit: BenefitVisitor) {
        totalBenefit += benefit.calcBenefit(this)
    }
}
class GoldMember : Member(){
    override fun calcBenefit(benefit: BenefitVisitor){
        totalBenefit += benefit.calcBenefit(this)
    }
}

interface BenefitVisitor {
    fun calcBenefit(member: BronzeMember): Int
    fun calcBenefit(member: SilverMember): Int
    fun calcBenefit(member: GoldMember): Int
}

class DiscountBenefit : BenefitVisitor {
    override fun calcBenefit(member: BronzeMember): Int {
        // bronzemember 특화로직일을 함
        return 10
    }

    override fun calcBenefit(member: SilverMember): Int{
        // silvermember 특화로직일을 함
        return 20
    }

    override fun calcBenefit(member: GoldMember): Int {
        // goldmember 특화로직일을 함
        return 30
    }
}

class CouponBenefit : BenefitVisitor {
    override fun calcBenefit(member: BronzeMember): Int {
        return 20
    }

    override fun calcBenefit(member: SilverMember): Int{
        return 30
    }

    override fun calcBenefit(member: GoldMember): Int {
        return 40
    }

}


class PointBenefit : BenefitVisitor {
    override fun calcBenefit(member: BronzeMember): Int {
        return 30
    }

    override fun calcBenefit(member: SilverMember): Int{
        return 40
    }

    override fun calcBenefit(member: GoldMember): Int {
        return 50
    }

}


fun main() {

    val memberBronze = BronzeMember()
    val memberSilver = SilverMember()
    val memberGold = GoldMember()

    val discount = DiscountBenefit()
    val coupon = CouponBenefit()
    val point = PointBenefit()


    memberBronze.calcBenefit(discount)
    memberBronze.calcBenefit(coupon)
    memberBronze.calcBenefit(point)

    memberSilver.calcBenefit(coupon)
    memberGold.calcBenefit(point)

    println("total : " + memberBronze.totalBenefit)


}

 

double dispaching이 일어나는것을 볼 수 있으며, if문은 없어졌지만, 먼가 장황스럽다고 느껴질수도 있다. 
Visitor Pattern의 의도는 데이터 구조와 처리를 분리하는 패턴인데, 사실 visitor패턴은 Gof의 모든 패턴중에 가장 논쟁이 많아질 수 있는 패턴이라 보여진다. Benefit은 member에 대해 속속들이 알아야 할 수가 있으며, 원래 객체(자료구조의 반대개념)가 가진 단점인 타입 추가에는 유연하나, 기능추가엔 많은 변경을 요하는 문제점에다가 여기서는 요소추가가 많아질 경우 바꿔야 할 경우까지 많아 진다.  다만 요소는 고정될 가능성이 크고, 로직부분이 많아질 가능성이 큰 시스템에서 유용 할 수 있으니 트레이드오프를 잘 계산해서 사용하면 될 듯하다.