[디자인 패턴] Bridge Pattern

전성훈·2023년 11월 2일
0

DesignPattern

목록 보기
8/18
post-thumbnail

주제: 각 속성의 세분화


브릿지 패턴이란 구현부에서 추상층을 분리하여 각자 독립적으로 변형할 수 있게 하는 패턴이다.

브릿지 패턴이란

  • Bridge 패턴은 구조적 디자인 패턴으로, 구현을 인터페이스로 분리하여 두 개가 독립적으로 변형할 수 있게 한다는 아이디어에 기반을 둔다. 즉, 추상화를 그 구현체로부터 분리하여 둘을 독립적으로 변형하거나 확장하게 할 수 있다.
  • 이 패턴의 주요 목표는 추상화와 구현 사이의 느슨한 결합을 유지하는 것이다. 이로 인해 시스템의 각 부분을 독립적으로 관리하고 확장할 수 있다.

브릿지 패턴의 구조

브릿지 패턴

  • Abstraction (추상화)
    • Client가 사용하는 최상위 타입
    • Implementation을 참조하며 일을 위임한다.
    • Refined Abstration으로 Abstarction을 확장할 수 있다.
  • RefinedAbstraction (정제된 추상화)
    • 추상화에서 제공하는 기능을 확장하는 클래스이다.
  • Implementor (구현자)
    • Abstraction의 기능을 구현하기 위해 인터페이스를 정의
    • 이 인터페이스는 모든 구현 클래스가 따라야 하는 인터페이스이다.
  • ConcreteImplementor (구체적인 구현자)
    • 실제로 추상 인터페이스를 구현하는 클래스이다.

장단점

장점

1. 추상화와 구현 간의 결합도를 줄인다.

  • 추상화와 구현이 분리되어 있어 둘 사이의 의존성이 줄어든다. 그 결과, 두 요소를 독립적으로 확장하거나 변경하는 것이 가능해진다.

2. 단일 책임 원칙을 준수한다.

  • 추상화와 구현이 각각 다른 일을 처리하도록 분리함으로써, 각 클래스는 자신의 책임 영역에만 집중하게 된다.

3. 시스템 구조를 개선한다.

  • 고수준의 추상화 코드와 저수준의 구현 코드를 분리함으로써, 더 깔끔하고 이해하기 쉬운 시스템 구조를 만들 수 있다.

단점

1. 복잡성이 증가한다.

  • Bridge 패턴은 코드를 좀 더 복잡하게 만들 수 있다. 단순한 솔루션에서는 오버킬(과도한 해결책)이 될 수 있다.

2. 추상화와 구현 간의 연결을 해줘야 한다.

  • 추상화와 구현을 연결하는 코드를 작성해야 한다. 이로 인해 초기 설정이 복잡해질 수 있다.

결론

  • 종류가 다양한 속성들을 지닌 타입을 분리해 독립적인 수정/확장을 할 때 사용한다.
    • OCP(Open-Closed Principle)를 만족한다
    • SRP(Single Responsibility Principle)를 만족한다
  • Abstraction은 Implementation을 참조로 지니고 모든 일을 위임한다.
    • Client는 최상위 타입 Abstraction의 로직만 사용하기 때문에 Implementation 로직을 몰라도 된다.
  • 다만 결합도가 높은 클래스에 적용할 경우 분리하기 힘들거나 타입이 많아져 오히려 코드가 더 복잡해질 가능성이 존재한다.

예시 코드

기본 구현

// Abstraction 
class RemoteControl { 
	var device: Device
	
	init(device: Device) { 
		self.device = device 
	}
	
	func togglePower() { 
		device.turnOn()
	}
}

// RefinedAbstraction 
class AdvancedRemoteControl : RemoteControl { 
	func mute() { 
		device.setValue(to: 0)
	}
}

// Interface 
protocol Device { 
	func turnOn()
	func setVolume(to: Int)
}

struct TV: Device { 
	func turnOn() { 
		print("Turn on TV")
	}
	
	func setValue(to percent: Int) { 
		print("Set TV valume to \(percent)")
	}
}

struct Radio: Device { 
	func turnOn() { 
		print("Turn on Radio")
	}
	
	func setValue(to percent: Int) { 
		print("Set radio valume to \(percent)")
	}
}

class의 상속 대신 protocol과 struct을 사용해서 구현

// Abstraction 
protocol RemoteControl { 
	var device: Deivce {get set}
	func togglePower()
}

struct BasicRemoteControl: RemoteControl { 
	var device: Device
	
	func togglePower() { 
		device.turnOn()
	}
}

// RefinedAbstraction 
struct AdvancedRemoteControl : RemoteControl { 
	var device: Device
	
	func togglePower() { 
		device.turnOn
	}
	
	func mute() { 
		device.setValue(to: 0)
	}
}

// Interface 
protocol Device { 
	func turnOn()
	func setVolume(to: Int)
}

struct TV: Device { 
	func turnOn() { 
		print("Turn on TV")
	}
	
	func setValue(to percent: Int) { 
		print("Set TV valume to \(percent)")
	}
}

struct Radio: Device { 
	func turnOn() { 
		print("Turn on Radio")
	}
	
	func setValue(to percent: Int) { 
		print("Set radio valume to \(percent)")
	}
}

Bridge 패턴을 활용한 Animation 설정

import UIKit

// 추상화
protocol Animation {
    var implementation: AnimationImplementation { get }
    func perfomAnimation(view: UIView)
}

class ScaleAnimation: Animation {
    let implementation: AnimationImplementation
    
    init(implementation: AnimationImplementation) {
        self.implementation = implementation
    }
    
    func perfomAnimation(view: UIView) {
        implementation.animate(view: view)
    }
}

class FadeAnimation: Animation {
    let implementation: AnimationImplementation
    
    init(implementation: AnimationImplementation) {
        self.implementation = implementation
    }
    
    func perfomAnimation(view: UIView) {
        implementation.animate(view: view)
    }
}

class RotationAnimation: Animation {
    let implementation: AnimationImplementation
    
    init(implementation: AnimationImplementation) {
        self.implementation = implementation
    }
    
    func perfomAnimation(view: UIView) {
        implementation.animate(view: view)
    }
}

// 구현
protocol AnimationImplementation {
    func animate(view: UIView)
}

// 구체적인 구현
class SpringAnimation: AnimationImplementation {
    func animate(view: UIView) {
        UIView.animate(withDuration: 1.0,
                       delay: 0,
                       usingSpringWithDamping: 0.5,
                       initialSpringVelocity: 1,
                       options: .curveEaseInOut
        ) {
            view.transform = CGAffineTransform(scaleX: 1.2, y: 1.2)
        } completion: { _ in
            UIView.animate(withDuration: 0.5, animations: {
                view.transform = CGAffineTransform.identity
            })
        }
    }
}

class KeyframeAnimation: AnimationImplementation {
    func animate(view: UIView) {
        UIView.animateKeyframes(withDuration: 1.0, delay: 0, options: []) {
            UIView.addKeyframe(withRelativeStartTime: 0.0, relativeDuration: 0.25) {
                view.transform = CGAffineTransform(scaleX: 1.2, y: 1.2)
            }
            UIView.addKeyframe(withRelativeStartTime: 0.25, relativeDuration: 0.75) {
                view.transform = CGAffineTransform.identity
            }
        } completion: { _ in
            return
        }
    }
}

class FadeAnimationImplementation: AnimationImplementation {
    func animate(view: UIView) {
        UIView.animate(withDuration: 0.5) {
            view.alpha = 0
        } completion: { _ in
            UIView.animate(withDuration: 0.5) {
                view.alpha = 1
            }
        }
    }
}

class RotationAnimationImplementation: AnimationImplementation {
    func animate(view: UIView) {
        UIView.animate(withDuration: 0.5) {
            view.transform = CGAffineTransform(rotationAngle: .pi)
        } completion: { _ in
            UIView.animate(withDuration: 0.5) {
                view.transform = CGAffineTransform.identity
            }
        }
    }
}

class ViewController: UIViewController {
    var animation: Animation = ScaleAnimation(implementation: SpringAnimation())
    
    var testView = UIView()
    var segementedControl = UISegmentedControl(items: ["Spring", "Keyframe", "Fade", "Rotation"])
    var button = UIButton()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view.
        
        makeLayout()
        makeAttribute()

    }

    func makeLayout() {
        view.backgroundColor = .white
        [
            segementedControl,
            testView,
            button
        ].forEach {
            $0.translatesAutoresizingMaskIntoConstraints = false
            view.addSubview($0)
        }
        
        NSLayoutConstraint.activate([
            segementedControl.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 48),
            segementedControl.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 16),
            segementedControl.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -16),

            testView.topAnchor.constraint(equalTo: segementedControl.bottomAnchor, constant: 80),
            testView.widthAnchor.constraint(equalToConstant: 200),
            testView.heightAnchor.constraint(equalToConstant: 250),
            testView.centerXAnchor.constraint(equalTo: segementedControl.centerXAnchor),
            
            button.centerXAnchor.constraint(equalTo: testView.centerXAnchor),
            button.topAnchor.constraint(equalTo: testView.bottomAnchor, constant: 30)
        ])
    }
    
    func makeAttribute() {
        testView.backgroundColor = .darkGray
        testView.layer.borderColor = UIColor.red.cgColor
        testView.layer.borderWidth = 1
        testView.layer.cornerRadius = 16
        
        button.setTitle("발동!", for: .normal)
        button.setTitleColor(.black, for: .normal)
        button.addTarget(self, action: #selector(didTapAnimateButton), for: .touchUpInside)
        
        segementedControl.selectedSegmentIndex = 0
        segementedControl.addTarget(self, action: #selector(didChangeAnimationType(_:)), for: .valueChanged)
    }
    
    @objc func didTapAnimateButton() {
        animation.perfomAnimation(view: testView)
    }
    
    @objc func didChangeAnimationType(_ sender: UISegmentedControl) {
        switch sender.selectedSegmentIndex {
        case 0:
            animation = ScaleAnimation(implementation: SpringAnimation())
        case 1:
            animation = ScaleAnimation(implementation: KeyframeAnimation())
        case 2:
            animation = FadeAnimation(implementation: FadeAnimationImplementation())
        case 3:
            animation = RotationAnimation(implementation: RotationAnimationImplementation())
        default:
            break
        }
    }
}

출처(참고문헌)

제가 학습한 내용을 요약하여 정리한 것입니다. 내용에 오류가 있을 수 있으며, 어떠한 피드백도 감사히 받겠습니다.

감사합니다.

0개의 댓글