티스토리 뷰

반응형

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

이번 포스트는 브릿지패턴에 대해서 이야기하려고 합니다.
브리지 사전전 정의는 기본적으로 다리 구조물을 뜻합니다. 다리의 역할은 강이나 두 지점을 건널 수 있게 만드는 시설을 의미하는데요. 왜 이름을 브리지 패턴으로 선언하고 브리지 패턴의 장점과 단점을 알아보겠습니다.

 

브리지 패턴을 왜 사용하는가?

브리지 패턴은 구조(Structural)패턴으로써 클래스 및 객체들읠 구성을 통하여 구현 뿐만 아니라 추상화된 부분까지 변경시켜야하는 경우에는 브리지 패턴을 사용하면됩니다.
예를들어서 만능 리모컨을 만든다고 가정합시다. 조금 더 편한 가정을 위해서 모든 TV의 리모콘은 똑같은 추상화된 부분으로 작업을 한다고 가정합니다.
리모컨 컨트롤의 추상클래스와 TV 라는 인터페이스를 서로 다른 클래스 계층구조에 집어 넣음으로써 각각 독립적으로 추가 요구사항에 변경할 수 있습니다.

  • Abstraction (RemoteControl)
    : 추상화된 인터페이스를 정의합니다. Implementor 객체에 대한 참조 유지 관리합니다.
  • RefinedAbstraction (ConcrereRemote)
    :
    추상화에 의해 정의된 인터페이스를 확장합니다.
  • Implementor(TV)
    :
    구현 클래스의 인터페이스를 정의합니다. 이 인터페이스는 Abstraction의 인터페이스와 일치 할 필요는 없습니다.
  • ConcreteImplementor (RCA, Sony)
    :
    Implementor의 인터페이스를 구현하고 구체적인 구현을 정의합니다.

 

소스코드를 통한 브리지 패턴 알아보기

UMl 로 알아본 브리지 패턴에 대해서 코드로 알아봅시다.

/**
 * 추상화된 인터페이스
 * Abstraction
 */
abstract class RemoteControl(var tvFactory: TvFactory?) {
    var tv: TV? = null

    open fun on(){
        tv?.on()
    }

    open fun off(){
        tv?.off()
    }

    // 구상 서브 클래스는 구현 클래스 계층구조 쪽의 메서드가 아닌 추상 클래스 쪽의 메소드를 통해서 구현
    open fun setChannel(channel: Int?){
        tv?.tuneChannel(channel)
    }

    open fun getChannel(): Int?{
        return tv?.getChannel()
    }

    open fun setTV(type: String?){
        try {
            tv = tvFactory?.getTV(type)
        }catch (e: Exception){
            e.printStackTrace()
        }
    }
}
/**
 * RefinedAbstraction
 */
class GenericRemote(tvFactory: TvFactory?) : RemoteControl(tvFactory) {
    fun nextChannel() {
        getChannel()?.let{channel->
setChannel(channel + 1)
}
}

    fun prevChannel() {
        getChannel()?.let{channel->
setChannel(channel - 1)
}
}
}

 

/**
 * implementor
 */
interface TV {
    fun on()
    fun off()
    fun tuneChannel(channel: Int?)
    fun getChannel(): Int?
}
/**
 * ConcreteImplementor
 */
class RCA: TV {
    companion object {
        private const val DEFAULT_CANNEL = 1
    }

    var channel = DEFAULT_CANNEL
    override fun on() {
        Log.d(MainApplication.TAG, "Turning on the RCA TV")
    }

    override fun off() {
        Log.d(MainApplication.TAG, "Turning off the RCA TV")
    }

    override fun tuneChannel(channel: Int?) {
        this.channel = channel ?: DEFAULT_CANNEL
        Log.d(MainApplication.TAG, "Set the RCA TV Channel to ${this.channel}")
    }

    override fun getChannel(): Int? {
        return channel
    }
}
class TvFactory {
    // RCA 외에 SONY, SAMSUNG 등 TV 타입을 다양하게 설정할 수 있다.

    companion object {
        const val RCA = "RCA"
    }

    @Throws(Exception::class)
    fun getTV(type: String?): TV{
        return when(type){
            RCA -> RCA()
            else -> throw Exception("Invalid TV Type")
        }
    }
}

 

브리지 패턴의 장점과 단점은?

장점

  • 구현을 인터페이스에 완전히 결합시키지 않았기 때문에 구현과 추상화된 부분을 구분할 수 있습니다.
    • RemoteControl 과 TV 계층의 분리
  • 추상화된 부분과 구현 부분을 독립적으로 확장 할 수 있습니다. 만약 추상화된 부분을 구현한 구상 클래스를 바꿔도 클라이언트 쪽에는 영향을 끼치지 않게됩니다.

단점

  • 인터페이스와 실제 구현부를 다른 방식으로 구현하기에 디자인이 복잡해질 수 있습니다.

 

마무리

브리지 패턴에 대해서 알아봤습니다.
구현 뿐만 아니라 추상화된 부분까지 변경이 필요할 때 브리지 패턴을 활용해서 독립적으로 확장하는 것에서 고민해도 좋을거 같습니다.

출처

[도서] Head First - Design Patterns

반응형
댓글