프로토콜 기반 설계 패턴을 집중적으로 다뤄보겠습니다. Swift에서는 “상속”보다 프로토콜을 중심으로 유연하고 확장성 있는 코드를 설계하는 것이 핵심입니다. 이 글에서는 실전에서 자주 사용되는 대표적인 패턴 Strategy, Delegate, Factory 패턴을 프로토콜을 활용해 어떻게 설계하고, 왜 이렇게 하면 더 좋은지를 예제와 함께 설명해드릴게요!
[Swift Apple ] Swift 프로토콜 기반 설계 패턴 |
🚀 Swift 프로토콜 기반 설계 패턴
– 유연하고 확장 가능한 코드 작성을 위한 전략!
✅ 왜 프로토콜 기반으로 설계할까?
-
상속(OOP) 은 계층 구조가 복잡해지고, 유연성이 떨어짐
-
프로토콜 기반 설계는:
-
다형성을 쉽게 구현
-
코드 재사용성과 테스트 효율을 높임
-
의존성을 낮추고 확장성 향상
-
Swift의 프로토콜 지향 프로그래밍(POP) 철학에 부합
-
1️⃣ Strategy 패턴 ✨
알고리즘을 런타임에 교체할 수 있는 패턴
프로토콜을 사용해 다양한 전략(방법)을 정의하고,
상황에 따라 전략을 쉽게 변경 가능!
예제: 결제 방식 선택하기
protocol PaymentStrategy {
func pay(amount: Int)
}
struct CardPayment: PaymentStrategy {
func pay(amount: Int) {
print("💳 카드로 \(amount)원 결제")
}
}
struct CashPayment: PaymentStrategy {
func pay(amount: Int) {
print("💵 현금으로 \(amount)원 결제")
}
}
class PaymentContext {
var strategy: PaymentStrategy
init(strategy: PaymentStrategy) {
self.strategy = strategy
}
func executePayment(amount: Int) {
strategy.pay(amount: amount)
}
}
사용하기:
let context = PaymentContext(strategy: CardPayment())
context.executePayment(amount: 5000) // 💳 카드로 5000원 결제
✅ 새로운 결제 방식이 추가돼도 코드 수정 없이 확장 가능!
Strategy 패턴 |
2️⃣ Delegate 패턴 🔄
객체 간 소통을 위한 대표적인 패턴
프로토콜을 통해 역할(Delegate) 을 정의하고,
이벤트나 데이터를 전달하는 구조.
예제: 다운로드 완료 알림
protocol DownloadDelegate: AnyObject {
func downloadDidFinish()
}
class Downloader {
weak var delegate: DownloadDelegate?
func startDownload() {
print("📥 다운로드 중...")
// 다운로드 완료 후 호출
delegate?.downloadDidFinish()
}
}
class ViewController: DownloadDelegate {
func downloadDidFinish() {
print("✅ 다운로드가 완료되었습니다!")
}
}
사용하기:
let downloader = Downloader()
let vc = ViewController()
downloader.delegate = vc
downloader.startDownload()
✅ iOS 개발(UITableView, UICollectionView 등)에서 가장 많이 활용!
Delegate 패턴 |
3️⃣ Factory 패턴 🏭
객체 생성을 프로토콜로 추상화
생성 로직을 감추고, 프로토콜 기반으로 유연하게 객체를 반환.
예제: 동물 객체 생성기
protocol Animal {
func speak()
}
struct Dog: Animal {
func speak() { print("멍멍!") }
}
struct Cat: Animal {
func speak() { print("야옹~") }
}
class AnimalFactory {
static func createAnimal(type: String) -> Animal {
switch type {
case "Dog": return Dog()
case "Cat": return Cat()
default: fatalError("알 수 없는 동물 타입")
}
}
}
사용하기:
let pet = AnimalFactory.createAnimal(type: "Dog")
pet.speak() // 멍멍!
✅ 객체 생성 방식이 바뀌어도 호출부는 변경 없음!
Factory 패턴 |
💡 프로토콜 기반 패턴의 장점
-
유연성: 새로운 기능 추가 시 기존 코드 변경 최소화
-
의존성 감소: 구체 타입이 아닌, 추상화(Protocol) 에 의존
-
테스트 용이성: Mock 객체로 쉽게 테스트 가능
-
확장성: Open-Closed Principle (확장에 열려 있고, 수정에는 닫혀 있음)
✏️ 실습 아이디어
-
Strategy 패턴으로 배송 방법 선택 시스템 만들기
(예: 빠른 배송, 일반 배송, 무료 배송)
-
Delegate 패턴으로 사용자 입력 이벤트 처리기 구현하기
(예: 버튼 클릭 시 이벤트 전달)
-
Factory 패턴으로 UI 요소 생성기 설계하기
(예: 테마별 버튼 생성)
✅ 1. Strategy 패턴 – 배송 방법 선택 시스템 만들기
// 1️⃣ 배송 전략 프로토콜
protocol DeliveryStrategy {
func deliver(item: String)
}
// 2️⃣ 전략 구현체들
struct FastDelivery: DeliveryStrategy {
func deliver(item: String) {
print("🚚 빠른 배송으로 \(item) 배송 중!")
}
}
struct StandardDelivery: DeliveryStrategy {
func deliver(item: String) {
print("📦 일반 배송으로 \(item) 배송 중!")
}
}
struct FreeDelivery: DeliveryStrategy {
func deliver(item: String) {
print("🐌 무료 배송으로 \(item) 배송 중 (시간 오래 걸림)")
}
}
// 3️⃣ 컨텍스트
class DeliveryContext {
var strategy: DeliveryStrategy
init(strategy: DeliveryStrategy) {
self.strategy = strategy
}
func send(item: String) {
strategy.deliver(item: item)
}
}
// 사용 예시
let fast = DeliveryContext(strategy: FastDelivery())
fast.send(item: "아이폰")
let free = DeliveryContext(strategy: FreeDelivery())
free.send(item: "책")
Strategy 패턴 – 배송 방법 선택 시스템 만들기 |
✅ 2. Delegate 패턴 – 사용자 입력 이벤트 처리기 구현하기
// 1️⃣ 델리게이트 프로토콜 정의
protocol ButtonDelegate: AnyObject {
func didTapButton()
}
// 2️⃣ 버튼 클래스
class CustomButton {
weak var delegate: ButtonDelegate?
func tap() {
print("🔘 버튼이 눌렸습니다.")
delegate?.didTapButton()
}
}
// 3️⃣ 델리게이트 구현체
class ViewController: ButtonDelegate {
func didTapButton() {
print("✅ 버튼 클릭 이벤트 처리 완료!")
}
}
// 사용 예시
let button = CustomButton()
let controller = ViewController()
button.delegate = controller
button.tap()
✅ weak 키워드로 순환 참조 방지 (클래스에서만 사용 가능)
Delegate 패턴 – 사용자 입력 이벤트 처리기 구현하기 |
✅ 3. Factory 패턴 – 테마별 UI 버튼 생성기
import UIKit
// 1️⃣ 버튼 스타일 프로토콜
protocol ThemedButton {
func createButton() -> UIButton
}
// 2️⃣ 구체적인 버튼 스타일
struct DarkThemeButton: ThemedButton {
func createButton() -> UIButton {
let button = UIButton(type: .system)
button.setTitle("Dark Button", for: .normal)
button.backgroundColor = .black
button.setTitleColor(.white, for: .normal)
return button
}
}
struct LightThemeButton: ThemedButton {
func createButton() -> UIButton {
let button = UIButton(type: .system)
button.setTitle("Light Button", for: .normal)
button.backgroundColor = .white
button.setTitleColor(.black, for: .normal)
return button
}
}
// 3️⃣ 팩토리
class ButtonFactory {
static func makeButton(theme: String) -> UIButton {
switch theme.lowercased() {
case "dark": return DarkThemeButton().createButton()
case "light": return LightThemeButton().createButton()
default: return UIButton(type: .system)
}
}
}
// 사용 예시 (UIKit 기반 프로젝트에서 ViewController 내부에 추가 가능)
let darkButton = ButtonFactory.makeButton(theme: "dark")
let lightButton = ButtonFactory.makeButton(theme: "light")
⚠️ 이 코드는 UIKit 환경에서만 작동합니다 (import UIKit 필요)
✅ 요약
패턴 |
기능 |
핵심 개념 |
---|---|---|
Strategy |
동적으로 배송 전략 선택 |
알고리즘 교체 |
Delegate |
버튼 클릭 이벤트 처리 |
역방향 통신 |
Factory |
스타일별 버튼 생성 |
객체 생성 분리 |
🎯 마무리 정리
패턴명 |
프로토콜 역할 |
활용 포인트 |
---|---|---|
Strategy |
다양한 알고리즘 정의 |
런타임 전략 변경 |
Delegate |
이벤트 전달 규칙 정의 |
객체 간 소통 |
Factory |
생성 방식 추상화 |
유연한 객체 생성 |
Swift 개발에서 프로토콜 기반 설계 패턴은
깔끔하고 확장성 있는 아키텍처를 만드는 필수 도구입니다.
댓글 쓰기