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에 대해 속속들이 알아야 할 수가 있으며, 원래 객체(자료구조의 반대개념)가 가진 단점인 타입 추가에는 유연하나, 기능추가엔 많은 변경을 요하는 문제점에다가 여기서는 요소추가가 많아질 경우 바꿔야 할 경우까지 많아 진다. 다만 요소는 고정될 가능성이 크고, 로직부분이 많아질 가능성이 큰 시스템에서 유용 할 수 있으니 트레이드오프를 잘 계산해서 사용하면 될 듯하다.