본문 바로가기
Engineering/SW Design

[디자인 패턴] 구조패턴 - 브릿지

by 쿨쥰 2024. 3. 28.

구조패턴 - 브릿지

 


이전 글 : 구조패턴 - 어댑터

2024.03.25 - [Engineering/SW Design] - [디자인 패턴] 구조패턴 - 어댑터

 

[디자인 패턴] 구조패턴 - 어댑터

구조패턴 - 어댑터 이전 글 : 구조패턴 - 싱글턴 2024.03.11 - [Engineering/SW Design] - [디자인 패턴] 생성패턴 - 싱글턴 [디자인 패턴] 생성패턴 - 싱글턴 생성패턴 - 싱글턴 이전 글 : 생성패턴 - 프로토타

skidrow6122.tistory.com

 

 

요약

간단히 요약하면 구현과 추상을 분리하는 패턴이다.

구현과 추상을 분리한다는 말만 보면 그냥 보통의 SW 아키텍처 원칙과 다를 바 없어 보이지만 여기서의 분리란 아래를 의미한다.

  • 큰 클래스 또는 밀접하게 관련된 클래스들의 집합을 두 개의 개별 계층구조(구현, 추상)로 나누고 각각 독립적으로 개발할 수 있도록 함

복수의 그룹을 가지는 개념이 혼재한다고 한다면.. ex) 색깔, 모양

각 인터페이스를 구현한 각각의 클래스들에서 추상층을 분리하여 각자 독립적으로 변형 및 확장 가능한 형태로 만드는 것이다.

즉, 두개의 다른 계층의 커플링을 약화시키며 협력은 가능하도록 하게하는 패턴이다.

 

키워드 : 구현과 추상을 분리

 

 

 

문제

모양 클래스가 있다고 가정하자.

이 모양 클래스를 구현한 Circle 클래스와, Square 클래스가 존재하는 상황이다.

 

이 클래스 계층 구조를 확장하여 색깔이 붙어야 한다고 가정해보자.

색깔은 빨간색과 파란색 두종류가 존재한다.

 

이런 상황에서 Circle 클래스와 Square 클래스에 빨간색, 파란색을 추가하다보면 이미 두개의 자식 클래스가 있으므로 결국
BlueCircle, RedCircle, BlueSquare, RedSquare 와 같이 4종류 조합의 클래스가 탄생하는 문제가 생긴다.

 

당연히 이 클래스들의 조합은 속성의 수에 따라 기하급수적으로 증가하기 마련이다.

 

 

해결책

이 문제는 모양, 색깔 이라는 두가지 독립적인 차원에서 모양 클래스들에대한 확장을 시도하기 때문에 발생하며, 이러한 클래스 상속 문제는 개발을 하다보면 허다하게 접하게 된다.

브릿지 패턴은 상속이라는 아이디어대신 객체 합성 이라는 방법으로 이를 해결한다.

즉, 차원 중에 하나를 별도의 클래스 계층 구조로 추출해낸 다음, 원래 클래스들이 하나의 클래스 내에서 모든 상태와 행동들을 갖는 대신 새 계층 구조의 객체를 참조하게 만드는 것이다.

다시 말해, 모양을 담당하는 Shape 와 색깔을 담당하는 Color 라는 두가지 대분류를 만들고,

Shape 인터페이스에서는 Color 객체 자체를 속성으로 갖게 하는 것이다.

이는 의존성을 주입하는 것과 흡사한데, 이 과정을 거치면 각 모양인 Circle 과 Square 에는 Color 를 프로퍼티로 가지게 되며, 클라이언트가 Color 를 주입해줌에 따라 모양의 색깔은 계속 바뀔 수 있다.

즉, 이 형태로 변경됨에 따라 Shape는 연결된 Color 객체에 모든 색상 관련 작업을 위임할 수 있게 되며, 이 참조는 Shape 와 Color 클래스들 사이의 브릿지 역할을 하게 된다.

 

 

 

구조

 

문제상황에서의 예시에 대입해보면, Abstract(추상) 은 Shape 에 해당하고, Interface Implementation 은 Color 에 해당한다.

 

 

Hands on

Switch 에는 기기를 켰다 껐다 할 수 있는 2종류의 기능이 있다.

가전제품 차원이 존재하고 여기에 TV 와 Monitor 라는 2종류의 기기가 존재한다.

이 가전 제품은 모두 Switch 에 있는 기능을 사용할 수 있다.

interface Switch {
    // 이것이 핵심!!
    var appliance: Appliance
    fun turnOn()
    fun turnOff()
}

class RemoteController(override var appliance: Appliance) : Switch {
    override fun turnOn() {
        appliance.run()
    }
    override fun turnOff() {
        appliance.reboot()
    }
}
  • 이 Switch 는 위 문제상황 예제를 참조하면 Shape (모양) 클래스에 해당한다.
  • 추상 레이어에 속하며, Appliance 라는 구현 객체를 포함하고 있다.
interface Appliance {
    fun run()
    fun reboot()
}

class TV : Appliance {
    override fun run() {
        println ("TV turned on")
    }
    override fun reboot() {
        println ("TV turned off")
    }
}

class Monitor : Appliance {
    override fun run() {
        println ("Monitor turned on")
    }
    override fun reboot() {
        println ("Monitor turned off")
    }
}
  • 이 예제에서 Appliance 는 위 문제상황에서의 Color (색상) 같은 구현에 해당한다.
  • Appliance 객체 자체는 추상 Switch 의 멤버 속성으로 들어가게 된다.
  • 이 Appliance 를 구현한 구상 구현체들에는 플랫폼 별 맞춤 코드가 들어간다.
fun main() {
    var tvRemoteController = RemoteController(appliance = TV())
    tvRemoteController.turnOn()
    tvRemoteController.turnOff()
    var monitorRemoteController = RemoteController(appliance = Monitor())
    monitorRemoteController.turnOn()
    monitorRemoteController.turnOff()
}
  • 클라이언트에서는 abstract 추상 클래스에 구현 객체를 담아서 주입해주고
  • 주입된 추상 클래스에서는 구현 객체에서의 함수를 자유자재로 쓸 수 있다.

 

핵심

  • 여러 직교(독립) 차원에서 클래스를 확장해야만 할때 사용
    • 각 차원에 대해 별도의 클래스 계층구조가 추출되어야 함
  • 플랫폼 독립적인 클래스들을 만들 수 있음
  • 브릿지는 일반적으로 사전에 미리 고려된 설계에서 구조가 만들어지며, 어댑터는 운영 중 기존과 호환되지 않았던 클래스들이 서로 잘 동작하게 만들기 위해 사용됨
  • 전략패턴과 헷갈리는데, 전략패턴은 구현부가 자주 바뀔 때 사용하고, 브릿지 패턴은 계층간 의존성을 낮추는데 사용됨
    • 즉, 전략 패턴은 클라이언트가 사용 시 자주 바뀔때 사용되고,
    • 브릿지 패턴은 클라이언트의 플랫폼에 따라 바뀌는 부분에 주로 사용됨

간단히 hands on 을 통해 브릿지 패턴을 살펴 보았다.

댓글