[Design Pattern] Delegate Pattern


Delegate Pattern


Delegate Pattern

OOP(객체지향프로그래밍)에서 하나의 객체가 모든 일을 처리하는 것이 아니라 일들 중 일부를 다른 객체에 넘기는 것

  • 효율성 관점에서 아주 중요
    • 기능을 위임할 수 잇는 객체가 있다는 것은 그만큼 직접 구현해야 하는 부분이 적다는 것이기 때문
  • 기능을 처리할 객체를 Delegate로 설정하고, 특정 이벤트가 발생할 때 이를 Delegate에 의해 위임된 본래의 객체로 전달해 줌
데이터를
받을 View
<->데이터를
전달할 View
- 프로토콜 채택
- 실제 구현
- 대리자 위임
<->- 타입이 프로토콜인
프로퍼티 생성
- Delegate 사용

사용하는 경우

  • 하나의 객체가 해야하는 일이 여러가지일 경우
  • 수신 받는 객체가 많을 때, 콜백 블럭을 받기 위한 목적이 분명할 경우
  • 내부의 블록을 호출시키는 코드를 읽고 다시 돌아와서 추적할 일이 없는 경우

사용하는 이유

  • Delegation은 한 클래스와 다른 클래스의 상호작용을 간단히 할 수 있도록 도움
  • 클래스 간 요구 사항을 전달해주는 프로토콜만 있으면 연결 수월해 짐
  • 완전한 클래스 또는 구조체를 상속할 필요가 없기 때문에 더욱 가볍게 사용할 수 있음
  • Delegate Pattern은 1:1 관계에서 매우 유용
    (1:N 또는 N:N 관계에는 Observer Pattern이 유용)
  • 델리게이트는 프로토콜을 준수하는 것 만으로 구현이 가능하므로 매우 유연
  • 제어하지 않는 코드 내에서 발생하는 이벤트를 연결할 수 있는 좋은 방법

사용 시 유의점

  • Delegate Pattern은 굉장히 유용하지만, 과도하게 사용될 위험성이 존재
    • 객체에 너무 많은 위임을 생성하지 않도록 주의 
  • 강한 참조 순환이 발생하지 않도록 유의
    • 보통 Delegate 프로퍼티의 경우 weak으로 선언

장점과 단점

장점

  • 재사용할 수 있는 코드를 작성 가능 (프로토콜의 장점)
  • 매우 엄격한 Syntax로 인해 프로토콜에 필요한 메서드들이 명확하게 명시
  • 컴파일 시 경고나 에러가 떠서 프로토콜의 구현되지 않은 메소드들을 알려줌
  • 로직의 흐름을 따라가기 쉬움
  • 프로토콜 메소드로 알려주는 것 뿐만 아니라 정보를 받을 수도 있음
  • 프로토콜이 컨트롤러의 범위 안에서 정의된다.

단점

  • Delegate 사용을 위해 구현해야 하는 코드가 많음
    • 다수의 객체들에게 이벤트를 호출하는 방식이 비효율적
    • 1:N 또는 N:N 관계에는 Observer Pattern이 적합
  • delegate 설정에 nil이 들어가지 않게 주의해야한다. 크래시를 일으킬 수 있다.

Delegate Pattern 예시

붕어빵 장사를 예로 든다면

  • protocol property : 붕어빵 제작 재료 (붕어빵 틀 / 반죽 / 불)
  • protocol method : 붕어빵 제작 방법 (불의 세기 / 익히는 정도)
  • protocol : 붕어빵 조리 메뉴얼
  • delegate : 메뉴얼을 통한 조리
// 자격 정의 (메뉴얼)
protocol FishBreadDelegate {
    // 붕어빵 제작 재료
    var fishFrame
    var dough
    var fire

    // 붕어빵 제작 방법
    func firePower()
    func cookTime()
}

// 붕어빵 제작 클래스
// 보통 직접적으로 유저와 커뮤니케이션하는 부분
class FishBreadCook {
    
    // 강한 참조 순환이 일어날 수 있으므로 약한 참조로 선언
    weak var delegate: FishBreadDelegate?
    
    func doSomething() {
        print("붕어빵이 만들어지고 있음")
    }
    
    // 누가 붕어빵을 만드는지 몰라도 됨
    func firePower() {
        delegate?.firePower()
    }
    func cookTime() {
        delegate?.cookTime()
    }
}

// 사장님 클래스
// 뷰컨트롤러의 역할
class Shopkeeper : FishBreadDelegate {

    init(manual: FishBreadCook) {
        manual.delegate = self
    }
    
    func firePower() {
        print("사장님이 불 세기를 조절한다.")
    }

    func cookTime() {
        print("사장님이 조리 시간을 조절한다.")
    }
}

// 알바생 클래스
// 뷰컨트롤러의 역할
class partTimer: FishBreadDelegate {

    init(manual: FishBreadCook) {
        manual.delegate = self
    }

    func firePower() {
        print("알바생이 불 세기를 조절한다.")
    }
    
    func cookTime() {
        print("알바생이 조리 시간을 조절한다.")
    }
}

// 레시피라는 이름의 붕어빵 조리법
let recipe = FishBreadCook

// 레시피를 메뉴얼로 붕어빵을 조리하는 가게매니저님과 꼬마
let shopmanager = Shopkeeper(manual: recipe)
let kkoma = partTimer(manual: recipe)

참고

zooneon.log
나무’s 블로그
상어의 개발 블로그
newmon tistory Blog
ggasoon2 tistory Blog
Ian’s Blog
엠아이노의 iOS tistory Blog
아리의 iOS 탐구생활 tistory Blog






© 2021. by hminkim