브릿지 패턴이란 구현부에서 추상층을 분리하여 각자 독립적으로 변형할 수 있게 하는 패턴이다.
- Client가 사용하는 최상위 타입
- Implementation을 참조하며 일을 위임한다.
- Refined Abstration으로 Abstarction을 확장할 수 있다.
- 추상화에서 제공하는 기능을 확장하는 클래스이다.
- Abstraction의 기능을 구현하기 위해 인터페이스를 정의
- 이 인터페이스는 모든 구현 클래스가 따라야 하는 인터페이스이다.
- 실제로 추상 인터페이스를 구현하는 클래스이다.
1. 추상화와 구현 간의 결합도를 줄인다.
- 추상화와 구현이 분리되어 있어 둘 사이의 의존성이 줄어든다. 그 결과, 두 요소를 독립적으로 확장하거나 변경하는 것이 가능해진다.
2. 단일 책임 원칙을 준수한다.
- 추상화와 구현이 각각 다른 일을 처리하도록 분리함으로써, 각 클래스는 자신의 책임 영역에만 집중하게 된다.
3. 시스템 구조를 개선한다.
- 고수준의 추상화 코드와 저수준의 구현 코드를 분리함으로써, 더 깔끔하고 이해하기 쉬운 시스템 구조를 만들 수 있다.
1. 복잡성이 증가한다.
- Bridge 패턴은 코드를 좀 더 복잡하게 만들 수 있다. 단순한 솔루션에서는 오버킬(과도한 해결책)이 될 수 있다.
2. 추상화와 구현 간의 연결을 해줘야 한다.
- 추상화와 구현을 연결하는 코드를 작성해야 한다. 이로 인해 초기 설정이 복잡해질 수 있다.
- OCP(Open-Closed Principle)를 만족한다
- SRP(Single Responsibility Principle)를 만족한다
- 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)")
}
}
// 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)")
}
}
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
}
}
}
제가 학습한 내용을 요약하여 정리한 것입니다. 내용에 오류가 있을 수 있으며, 어떠한 피드백도 감사히 받겠습니다.
감사합니다.