티스토리 뷰

반응형

Head First - Design Patterns 의 템플릿 메소드 패턴 기반으로 작성하였습니다. 자세한 설명은 도서를 참고해주세요.

역할 사슬 패턴이라고 들어보셨나요? 역할 사슬은 한 요청을 두 개 이상의 객체에서 처리하고 싶을 때가 있습니다. 개발자의 성향상 if 문을 클래스 내에서 길게 사용하는 것을 지양하는 분이 계시다면 역할 사슬 패턴에 대해서 알아두면 좋을거 같습니다.

 

역할 사슬 패턴이란?

역할 사슬 패턴은 행위패턴으로 한 요청을 두 개 이상의 객체에서 처리할 때 사용하는 패턴입니다.

 장점
요청을 보낸 쪽하고 받는 쪽을 분리 할 수 있습니다.
사슬의 구조를 몰라도 되고 그 사슬에 들어있는 다른 객체에 대한 레퍼런스를 가질 필요가 없습니다.
사슬에 들어가는 객체 변경 및 순서 변경을 동적으로 가능합니다.

단점
실행 시 과정을 살펴보는 등 디버깅하기가 어려울 수 있습니다.

 

코드를 통한 역할 사슬 알아보기

한 대표가 있습니다. 그 대표는 자신에게 들어오는 메일 구분을 자동으로하는 검출기를 만들고 싶어합니다. 요청 메일이 어떤 메일인지 검출할 수 있는 AI는 만들어 놓은 상태로, 메일을 분류할 수 있는 디자인만 만들면 됩니다.

abstract class MailHandler(private val type: MailType) {
    private var next: MailHandler? = null

    fun next(hd: MailHandler): MailHandler{
        next = hd
        return hd
    }

    fun run(mail: Mail){
        when {
            filter(mail) -> {
                done(mail)
            }
            next != null -> {
                next?.run(mail)
            }
            else -> {
                fail(mail)
            }
        }
    }

    protected fun done(mail: Mail){
        println("${mail.title} 이 메일 종료는 $type 입니다.")
    }

    protected fun fail(mail: Mail){
        println("어떠한 조건도 걸리지 않은 메일입니다. Mail : ${mail.title}")
    }

    protected abstract fun filter(mail: Mail): Boolean
}

역할 사슬을 만들기 위한 abstract class로 successor, done, fail, next Fuction은 구성되어 있고, filter Fuction은 abstract 으로 만들어서 사슬에 들어있는 객체가 각각 요청에 따른 처리를 할 수 있도록 구성합니다.

/** 스팸 검사기 */
class SpamMailHandler(type: MailType) : MailHandler(type) {
    override fun filter(mail: Mail): Boolean {
        return mail.title?.contains("광고") ?: false
    }
}

/** 항의메일 검사기 */
class ComplainMailHandler(type: MailType) : MailHandler(type) {
    override fun filter(mail: Mail): Boolean {
        return mail.type == MailType.COMPLAIN
}
}

/** 가맹점 오픈 요청 메일 검사기 */
class NewMailHandler(type: MailType) : MailHandler(type) {
    override fun filter(mail: Mail): Boolean {
        return mail.type == MailType.NEW
}
}

/** 팬클럽 메일 검사기 */
class FanMailHandler(type: MailType) : MailHandler(type) {
    override fun filter(mail: Mail): Boolean {
        return mail.type == MailType.FAN
}
}

사슬 객체로써 각 주어진 요청에 따른 처리를 구성합니다.

enum class MailType {
    SPAM, COMPLAIN, FAN, NEW
}
data class Mail(
    val title: String? = null,
    val type: MailType? = null
)
/**
 * 메일 타입은 인공지능에서 자동 분류해준다고 가정하에 역할 사슬 패턴을 알아보겠습니다.
 */
class MainApplication {
    companion object {
        val TAG = MainApplication::class.simpleName

        @JvmStatic fun main(args : Array<String>) {
            val span = SpamMailHandler(MailType.SPAM)
            val fan = FanMailHandler(MailType.FAN)
            val complain = ComplainMailHandler(MailType.COMPLAIN)
            val new = NewMailHandler(MailType.NEW)

            // 역할 사슬로 묶는다.
            span.next(fan).next(complain).run(new)

            // 랜던 메일
            val mailList =arrayListOf(Mail("정말 팬입니다!", MailType.FAN),
                Mail("신규 매장 오픈하고 싶어요.", MailType.NEW),
                Mail("[광고] 좋은 제품이 있습니다!", MailType.SPAN),
                Mail("이것보시오!!", MailType.COMPLAIN),
            )

            for(mail in mailList) {
                span.successor(mail)
            }
        }
    }
}

사슬 구성 및 테스트를 위해 메일 리스트를 구성하였습니다.

실행 결과 화면

 

마무리

역할 사슬 패턴은 서두에 언급했듯이 복잡한 if-else 문을 분리하여 처리할 수 있습니다. 개발자의 성향에 따라서 역할 사슬 패턴을 활용한 코드를 볼 수 있습니다. 이번 포스트를 통해 다양항 코드를 보다 쉽게 디버깅 할 수 있으면 좋겠습니다.

반응형

'프로그래밍 > Design Patterns' 카테고리의 다른 글

인터프리터(Interpreter) 패턴  (0) 2022.02.02
플라이웨이트(Flyweight) 패턴  (0) 2022.01.26
빌더(Builder) 패턴  (0) 2022.01.20
브리지(Bridge) 패턴  (0) 2022.01.14
템플릿 메소드 패턴  (0) 2021.09.27
댓글