Structural Patterns 두 번째 키워드는 Bridge입니다.
1. 목적
관련된 클래스들의 집합을 두 개의 개별 계층 구조(추상화(Abstraction) 및 구현(Implementation))로 나눈 후 각각 독립적으로 개발할 수 있도록 하는 패턴입니다.

오늘도 무슨 말인지 잘 모르겠습니다!
2. 문제 상황
만약 Shape 클래스에게 Circle, Square 두 자식 클래스가 있다고 가정합시다.
class Shape {
// Code...
}
class Circle: Shape {
// Code...
}
class Square: Shape {
// Code...
}
추후 시간이 흘러 Red와 Blue 색상을 도입하여 계층 구조를 확장하고자 합니다.
하지만 이미 두 개의 자식 클래스가 있으므로 있을 수 있는 경우를 나누어 자식 클래스를 정의합니다.
class Shape {
// Code...
}
class RedCircle: Shape {
// Code...
}
class BlueCircle: Shape {
// Code...
}
class RedSquare: Shape {
// Code...
}
class BlueSquare: Shape {
// Code...
}

점점 클래스가 많아지는 것이 느껴지나요?
재질과 같은 새로운 기준 추가되거나 Yellow 색상과 같은 기존의 기준이 추가될 경우,
새로운 자식 클래스를 만들어야 하고 코드의 양이 기하급수적으로 증가할 것입니다.
핵심이 되는 문제는 무엇일까요?
Shape과 Color와 같이 독립적인 속성을 가지고 클래스들을 확장하고자 한 것이 핵심 문제입니다.
3. 해결책
Bridge 패턴을 사용하여 이 문제를 해결해보고자 합니다.
두 속성 중 한 가지를 선택하여 별도의 클래스 계층구조로 추출합니다.
원래 클래스들이 한 클래스 내에서 모든 상태와 행동들을 갖는 대신 새 계층 구조의 객체를 참조하도록 한다는 것입니다.

예시를 통해 이해해봅시다.
Shape에는 Circle과 Square 자식 클래스가 있었습니다.
이처럼 Color 부모 클래스를 만들고 Red와 Blue 자식 클래스를 만들어 Color에 대한 계층 구조를 추출합니다.
그리고 Shape 클래스에 색상 객체들 중 하나를 가리키는 참조 속성을 생성합니다.
class Shape {
// Color 참조
var color: Color
// Code...
}
class Circle: Shape {
// Code...
}
class Square: Shape {
// Code...
}
class Color {
// Code...
}
class Red: Color {
// Code...
}
class Blue: Color {
// Code...
}
3-1. 추상화(Abstraction)와 구현(Implementation)
Bridge 패턴을 정의하는 문장에 추상화(Abstraction)와 구현(Implementation)이라는 단어가 등장합니다.
정확하게 두 단어가 어떤 의미인지 이해하고 넘어갑시다.
추상화(Abstraction)는 인터페이스(Interface)라고도 하며 일부 개체(Entity)에 대한 상위 수준의 제어 레이어입니다.
추상화가 직접 기능을 수행하는 것이 아닌 플랫폼(Platform)이라고 불리는 구현(Implementation) 레이어에 위임(Delegate)해야 합니다.
이 타이밍에 여러분은 Swift의 protocol, Java의 interface, Java와 C++의 abstract class 등이 떠오를 것입니다.
물론 Bridge 패턴을 설계하는 과정에서 프로그래밍 언어의 기능이 사용될 수 있습니다만,
여기서 이야기하는 추상화는 이러한 프로그래밍 언어의 문법적인 키워드를 이야기하는 것이 아닙니다.
디자인패턴의 구조적인 요소로서의 Abstraction와 Implementation 자체로 이해하는 것이 중요합니다.
Abstraction와 Implementation는 서로 분리되어 독립적으로 확장될 수 있도록 하는 것이 핵심인 구조적 설계를 의미합니다.
앞의 설명과 동시에 Abstraction는 기능 레이어라고도 이해할 수 있습니다.
Client와 상호작용하는 계층으로, 무엇(What)을 수행해야 하는지를 정의합니다.
반면 Implementation에서는 어떻게(How) 수행하는지에 대해서 정의합니다.
이때 중요한 점은 Implementation이 Abstraction과 독립적으로 존재하기 때문에 하나의 Abstraction에 대해 서로 다른 방식을 갖는 Implementation가 존재할 수 있습니다.
4. 구조
아래 구조 이미지와 문제 상황의 Shape, Color 예시를 통해 실질적인 이해를 해봅시다.

Shape와 Color 중 좀 더 행동의 주체가 되어 외부 Interface와 상호작용하는 것은 Shape입니다.
Shape는 Abstraction, Color는 Implementation의 역할을 하게 됩니다.
Circle과 Square는 Refined Abstraction, Red와 Blue는 Concrete Implementation이 됩니다.
사진의 순서대로 코드를 작성해 보도록 합시다.
4-1. Abstraction
Abstraction는 무엇(What)을 수행해야 하는지에 집중합니다.
// Abstraction
class Shape {
// i: Implementation
var color: Color
init(color: Color) {
self.color = color
}
// feature1()
func changeShapeOpacity() {
color.changeOpacity(value: 0.5)
}
// feature2()
func printColorInfo() {
color.printColorName()
color.printColorHexValue()
}
}
Shape은 Color의 Method들이 어떻게 작동하지 전혀 알 수 없습니다.
그저 Color가 위와 같은 작업을 수행한다는 사실만 알 수 있습니다.
4-2. Implementation
// Implementation
protocol Color {
func changeOpacity(value: Float) // method1()
func printColorName() // method2()
func printColorHexValue() // method3()
}
Implementation는 어떻게(How) 수행할지에 대한 정의가 필요합니다.
이 단계까지만 보면 사실 어떻게 각 함수들이 작동하지는 알 수 없습니다.
4-3. Concrete Implementations
구체적인 구현체(Concrete Implementations)를 통해 각 함수들이 어떻게 작동할 것인지 정의합니다.
// Concrete Implementation
class Red: Color {
func changeOpacity(value: Float) {
print("Change Opacity to : \(value)")
}
func printColorName() {
print("Red")
}
func printColorHexValue() {
print("#FF0000")
}
}
// Concrete Implementation
class Blue: Color {
func changeOpacity(value: Float) {
print("Change Opacity to : \(value)")
}
func printColorName() {
print("Blue")
}
func printColorHexValue() {
print("#0000FF")
}
}
4-4. Refined Abstraction(Optional)
또한 Abstraction의 확장을 위해 Refined Abstraction을 정의할 수 있습니다.
// Refined Abstraction
class Circle: Shape {
var radius: Float
init(radius: Float, color: Color) {
self.radius = radius
super.init(color: color)
}
func getCircumference() -> Float {
self.radius * Float.pi
}
}
// Refined Abstraction
class Square: Shape {
var size: Float
init(size: Float, color: Color) {
self.size = size
super.init(color: color)
}
func getperimeter() -> Float {
self.size * 4
}
}
4-5. Client
마지막으로 Client에서는 Abstraction(또는 Refined Abstraction)과 상호작용하게 됩니다.
// Client
let redCircle = Circle(radius: 5.0, color: Red())
redCircle.printColorInfo()
// Red
// #FF0000
let circumference = redCircle.getCircumference()
print(circumference) // 15.707962
5. 장단점
SOLID 원칙 중 OCP(Open/Closed Principle, 개방/폐쇄 원칙)과 SRP(Single Responsibility Principle, 단일 책임 원칙)이 준수됩니다.
Client 코드에는 추상화(Abstraction) 코드만 사용되기 때문에 플랫폼의 세부 정보가 노출되지 않습니다.
하지만 결합도가 이미 높은 클래스라면 코드를 더 복잡하게 만들 수 있습니다.
6. 실제 사용 사례
iOS에는 여러 가지 데이터 베이스가 존재합니다.
이때 저장소의 구체적인 구현 방식과 데이터 저장 등의 인터페이스를 분리하여 설계할 수 있습니다.
// Implementation
protocol DataStore {
func save(data: String)
func fetch() -> String
}
// Concrete Implementation
class CoreDataStore: DataStore {
func save(data: String) {
print("Saving data to Core Data: \(data)")
}
func fetch() -> String {
return "Data from Core Data"
}
}
// Concrete Implementation
class RealmStore: DataStore {
func save(data: String) {
print("Saving data to Realm: \(data)")
}
func fetch() -> String {
return "Data from Realm"
}
}
// Abstraction
class DataManager {
// Implementation
private var dataStore: DataStore
init(dataStore: DataStore) {
self.dataStore = dataStore
}
func saveData(data: String) {
dataStore.save(data: data)
}
func fetchData() -> String {
return dataStore.fetch()
}
}
// Client
let coreDataManager = DataManager(dataStore: CoreDataStore())
coreDataManager.saveData(data: "User data")
let realmDataManager = DataManager(dataStore: RealmStore())
realmDataManager.saveData(data: "Product data")
Conclusion
Bridge 패턴은 기능과 구현의 분리가 필요할 때 사용할 수 있습니다.
예시 코드를 통해 봤듯 Swift에서는 Protocol과 상속을 활용하여 이미 다양한 곳에서 사용되고 있는 패턴입니다.
실제 사용 예시뿐만 아니라 UI 컴포넌트의 스타일과 기능, 알림 전송 방식, 네트워크 요청 등 다양한 영역에서 Bridge 패턴을 사용하면,
구조적 유연성을 높이고, 코드의 확장성을 보장할 수 있습니다.
Reference
https://refactoring.guru/design-patterns/bridge
Bridge
The bigger a class becomes, the harder it is to figure out how it works, and the longer it takes to make a change. The changes made to one of the variations of functionality may require making changes across the whole class, which often results in making e
refactoring.guru
'Design Pattern > GoF - Structural Patterns' 카테고리의 다른 글
[GoF Design Patterns] Flyweight (2) | 2024.11.28 |
---|---|
[GoF Design Patterns] Facade (0) | 2024.11.27 |
[GoF Design Patterns] Decorator (3) | 2024.11.13 |
[GoF Design Patterns] Composite (0) | 2024.11.10 |
[GoF Design Patterns] Adapter (1) | 2024.10.24 |