이전 글 [TroubleShooting] 디자인 패턴 적용기 - 1과 이어지는 내용입니다. 보고 오신다면 맥락을 이해하는 데에 도움이 됩니다.
현재 개발 중인 Kraton VPN 앱에서는 기존에 VPNController가 Protocol 인터페이스 구현체에 by 키워드를 통해 위임하고 있었습니다.
class VPNController(private val protocol: Protocol) : Protocol by protocol
이 구조는 상속에 비해 SOLID 원칙을 위배하지 않고 간결하다는 장점이 있지만, 실행 중 프로토콜을 바꾸는 기능 요구사항이 생기면서 더 이상 적절하지 않게 되었습니다.
Kotlin의 by 키워드는 컴파일 타임에 위임 메소드들을 생성하므로, VPNController를 생성할 때 주입한 protocol에 영구적으로 묶이게 됩니다.
protocol를 var로 선언한 후 값을 바꾸더라도 위임은 프로퍼티가 아닌 파라미터의 영향을 받기 때문에, connect()나 disconnect() 등은 여전히 초기 인스턴스를 호출할 뿐더러, 메모리 누수의 우려까지 있습니다.
즉, 실행 중에 WireGuard → IKEv2로 변경해도 동작은 여전히 WireGuard에 고정됩니다.
전략 패턴은 런타임에 객체의 동작을 교체 가능한 전략 객체로 분리하는 설계 방식입니다.
이 패턴을 사용하면 다음과 같은 요구사항을 만족할 수 있습니다:
interface Protocol {
fun connect()
fun disconnect()
fun status(): String
}
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"
}
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}")
}
}
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를 고려했을 때 위임은 분명 좋은 출발점이지만, 변화하는 요구사항에 맞춰 전략 패턴을 선택하였습니다.