[TroubleShooting] 디자인 패턴 적용기 - 2

Larry·2025년 7월 12일

TroubleShooting

목록 보기
2/2

이전 글 [TroubleShooting] 디자인 패턴 적용기 - 1과 이어지는 내용입니다. 보고 오신다면 맥락을 이해하는 데에 도움이 됩니다.

요구사항 변경: 실행 중 프로토콜을 바꿔야 한다면?

현재 개발 중인 Kraton VPN 앱에서는 기존에 VPNController가 Protocol 인터페이스 구현체에 by 키워드를 통해 위임하고 있었습니다.

class VPNController(private val protocol: Protocol) : Protocol by protocol

이 구조는 상속에 비해 SOLID 원칙을 위배하지 않고 간결하다는 장점이 있지만, 실행 중 프로토콜을 바꾸는 기능 요구사항이 생기면서 더 이상 적절하지 않게 되었습니다.

by 위임의 한계에 부딪히다..

Kotlin의 by 키워드는 컴파일 타임에 위임 메소드들을 생성하므로, VPNController를 생성할 때 주입한 protocol에 영구적으로 묶이게 됩니다.

protocol를 var로 선언한 후 값을 바꾸더라도 위임은 프로퍼티가 아닌 파라미터의 영향을 받기 때문에, connect()나 disconnect() 등은 여전히 초기 인스턴스를 호출할 뿐더러, 메모리 누수의 우려까지 있습니다.
즉, 실행 중에 WireGuard → IKEv2로 변경해도 동작은 여전히 WireGuard에 고정됩니다.

해결책: 전략 패턴(Strategy Pattern)

전략 패턴은 런타임에 객체의 동작을 교체 가능한 전략 객체로 분리하는 설계 방식입니다.
이 패턴을 사용하면 다음과 같은 요구사항을 만족할 수 있습니다:

  • VPN 연결 도중에도 프로토콜을 자유롭게 변경 가능
  • 새 프로토콜을 추가해도 기존 로직은 건드릴 필요 없음 (OCP 준수)
  • VPNController는 실행 흐름만 제어, 구체적 동작은 위임

전략 패턴으로 바꾼 구현

1. Protocol 인터페이스

interface Protocol {
    fun connect()
    fun disconnect()
    fun status(): String
}

2. 각 프로토콜 구현체

class IKEv2 : Protocol {
    override fun connect() = println("IKEv2: Establishing secure tunnel")
    override fun disconnect() = println("IKEv2: Tearing down connection")
    override fun status() = "IKEv2 Connected"
}

class WireGuard : Protocol {
    override fun connect() = println("WireGuard: Setting up fast, lightweight tunnel")
    override fun disconnect() = println("WireGuard: Disconnecting tunnel")
    override fun status() = "WireGuard Connected"
}

3. VPNController: 명시적 위임 + 전략 변경 가능

class VPNController(private var protocol: Protocol) {

    fun connect() = protocol.connect()

    fun disconnect() = protocol.disconnect()

    fun status(): String = protocol.status()

    fun updateProtocolConnection(newProtocol: Protocol) {
    	protocol.disconnect()
        println("Updating protocol to ${newProtocol.javaClass.simpleName}")
        protocol = newProtocol
        protocol.connect() // IKEv2: Establishing secure tunnel
    }

    fun logStatus() {
        println("Current protocol: ${protocol.javaClass.simpleName}")
    }
}

4. 사용 예시: 실행 중 프로토콜 전환

fun main() {
    val controller = VPNController(WireGuard())
    controller.connect()              // WireGuard: Setting up fast, lightweight tunnel
    controller.logStatus()           // Current protocol: WireGuard
    controller.updateProtocolConnection(IKEv2()) // Updating protocol to IKEv2 
    controller.logStatus()           // Current protocol: IKEv2
}

결론

동적으로 프로토콜을 변경할 필요가 없던 초기 설계에서는 Kotlin의 by 키워드 기반 위임 패턴이 간결하고 효율적이었습니다.
그러나 런타임 중 프로토콜 전환이라는 요구사항이 발생하면서, 전략 패턴으로 설계를 전환하는 것이 필수적이 되었습니다.
VpnController와 Protocol, 그리고 각 구현체인 Wireguard와 IKEv2를 고려했을 때 위임은 분명 좋은 출발점이지만, 변화하는 요구사항에 맞춰 전략 패턴을 선택하였습니다.

profile
안드로이드 개발자 Larry입니다.

0개의 댓글