새소식

인기 검색어

iOS/iOS

[iOS] Delegate 패턴 이해하기 및 Protocol 프로그래밍을 지향하는 이유

  • -

오늘 알아볼 것은 Delegate 패턴입니다. 알고보면 굉장히 쉽고 우리가 일상생활에서 많이 발견할 수 있는 패턴입니다.

Delegate의 사전적 정의는 위임자라고 나옵니다. 어려운 애기는 넘어가고 쉽게 쉽게 가봅시당 

 

일상생활을 예시로 먼저 설명드려볼게요.

 

저는 교촌치킨 매장에가서 레드콤보를 먹으려고 합니다.

여기서 제가 레드콤보를 먹기위해 해야하는 일은 무엇일까요 ?

 

저는 그냥 매장에가서 직원분께 메뉴를 말하고 그에 맞는 금액을 결제하면 끝입니다. 그러면 레드콤보를 직원분이 가져다 주시죠.

이것이 바로 델리게이트 패턴입니다. 

엥 이게 왜 델리게이트 패턴이야? 라고 하시는분들을 위해 조금 더 설명해드리자면 

우리는 레드콤보를 주문하여 레드콤보가 나온것이지만 사실 우리가 주문을하고 레드콤보가 나오기까지 아주 많은일들이 일어나죠

먼저 직원분은 매장포스기에 레드콤보를 추가하고 결제를 한뒤 주방에다가 레드콤보가 하나 있다고 알려줍니다.

그럼 조리를 하시는분이 치킨을 튀김기에 넣고 튀기고 다 튀긴 치킨을 양념으로 버무린뒤 홀서빙직원에게 레드콤보가 나왔다고 알려주죠

이 과정이 끝나면 홀직원분이 저희에게 주문한 레드콤보를 가져다주십니다.

 

하지만 보통 우리가 치킨을 주문할때 저러한 조리과정을 생각하시나요 ??? 맞습니다 생각할 필요도 신경을 쓸 필요도 없죠

저희는 그냥 직원에게 치킨을 주문하면 치킨이 나온다는것만 알고있으면 끝이니까요.

그 사이에 치킨이 만들어지는 과정은 직원분께 맡기는것입니다. 

 

왜 일상생활에서 쉽게 볼 수 있다는것인지 이해가 가시나요 ?? 

 

델리게이트 패턴은 상대가 무엇을 할 수 있는지만 알고 있으면 끝이라는 것입니다.

 

위 상황을 코드로 바꾸면 아래와 같습니다.

 

제가 한것이라곤 직원에게 메뉴와 결제방식만 알려주었을 뿐 나머지 일들은 직원이 알아서 다 해주었습니다.

 

 

 

다른 예시를 하나 더 들어볼게요

 

저는 스타벅스에 가서 아이스 아메리카노가 마시려고 합니다. 

 

그럼 제가 해야하는 일은 무엇일까요 ?

 

직원에게 아이스 아메리카노를 주문한뒤 카드건네면 끝입니다.

카드로 결제하고 아이스 아메리카노를 만드는 것들은 직원분이 해야하는 일이죠.

 

이게 바로 델리게이트 패턴입니다.

 

위 상황을 코드로 바꾸면 아래와 비슷할겁니다.

 

 

제가 알아야할것은 스타벅스 직원만알면 끝입니다. 

그 다음 메뉴와 결제방식을 직원에게 알려주면 끝인것이죠.

나머지일들은 직원이 알아서 처리해주었죠.

 

 

위 코드를 조금만 수정하면 우리에게 익숙한 패턴으로 바꿀 수 있습니다.

 

 

위와같이 나는 스타벅스직원에게 주문을 하기만 하면 제조된 커피를 건네받을 수 있습니다.

delegate에 타입을 명시해놓았기 때문에 스타벅스직원이 무슨일을 하는지 알고 있으니까요.

 

// 나는 스타벅스직원의 Type을 알고 있기 때문에 스타벅스 직원이 무슨일을 하는지 알고있음.
var delegate: 스타벅스직원?

 

이정도면 Delegate 패턴을 이해하셨을거라고 생각합니다.

 

하지만 위 코드는 사소한 문제가 있는데 어떤 문제가 있을까요 ???

 

바로 스타벅스가 아닌 다른 매장에가면 주문을 하지 못한다는 것입니다.

 

사실 당연한 문제입니다. 메가커피에가서 스타벅스 메뉴를 주문할 순 없으니까요.

 

그럼 스타벅스와 메가커피에서 주문을 하게 코드를 바꾸어 본다면 아래와 같을겁니다.

 

import Foundation

enum 스타벅스메뉴 {
    case 아이스아메리카노
    case 핑크플라워유스베리티
}

enum 메가커피메뉴 {
    case 아이스아메리카노
    case 쿠키프라페
}

enum 결제방식 {
    case 신용카드
    case 현금
    case 애플페이
    case 삼성페이
}

class 커피를마시고싶은나 {
    var 스타벅스직원: 스타벅스직원?
    var 메가커피직원: 메가커피직원?
    
    func 스타벅스주문(메뉴: 스타벅스메뉴, 결제방식: 결제방식) {
        print("나 : \(메뉴) 주세요")
        스타벅스직원!.주문(메뉴: 메뉴, 결제방식: 결제방식)
        print("나 : 주문한 \(메뉴) 마시기")
    }
    
    func 메가커피주문(메뉴: 메가커피메뉴, 결제방식: 결제방식) {
        print("나 : \(메뉴) 주세요")
        메가커피직원!.주문(메뉴: 메뉴, 결제방식: 결제방식)
        print("나 : 주문한 \(메뉴) 마시기")
    }
}

class 스타벅스직원 {
    func 주문(메뉴: 스타벅스메뉴, 결제방식: 결제방식) -> 스타벅스메뉴 {
        print("홀직원 : \(메뉴) 주문받았습니다. \(결제방식)으로 결제하겠습니다.")
        조리(메뉴: 메뉴)
        print("홀직원 : 주문하신 \(메뉴) 나왔습니다.")
        return 메뉴
    }
    
    private func 조리(메뉴: 스타벅스메뉴) -> 스타벅스메뉴 {
        print("조리직원 : \(메뉴) 조리시작")
        print("조리직원 : \(메뉴) 조리완료")
        return 메뉴
    }
}


class 메가커피직원 {
    func 주문(메뉴: 메가커피메뉴, 결제방식: 결제방식) -> 메가커피메뉴 {
        print("홀직원 : \(메뉴) 주문받았습니다. \(결제방식)으로 결제하겠습니다.")
        조리(메뉴: 메뉴)
        print("홀직원 : 주문하신 \(메뉴) 나왔습니다.")
        return 메뉴
    }
    
    private func 조리(메뉴: 메가커피메뉴) -> 메가커피메뉴 {
        print("조리직원 : \(메뉴) 조리시작")
        print("조리직원 : \(메뉴) 조리완료")
        return 메뉴
    }
}

 

 

메가커피직원을  추가했지만 문제가 해결된것처럼 보이시나요 ???

 

만약 투썸에서 주문을 하고 싶다면요 ?? 

 

그러면 다시 다른 커피브랜드를 추가하고 또 함수를만들고 직원을 추가해주어야합니다.

하물며 개인카페라면요 ?? 얼마나 많은 카페들이 있는데 그에맞는 함수를 추가하고 그에맞는 클래스를 일일이 변수로 추가하다보면 

커피를주문하고싶은나는 너무나 많은 변수들과 함수들이 추가되어있겠죠

 

이때 필요한것이 프로토콜입니다.

 

스타벅스, 메가커피, 투썸, 개인카페 등등 카페가 바뀌어도 우리가 할일과 직원이 해야할일은 정해져있습니다.

 

우리는 직원에게 각 브랜드에 맞는 메뉴를 주문하면 끝이고

직원은 주문을 받은 메뉴를 만들어주면 끝이기 때문이죠

 

이를 프로토콜로 정의해보면

protocol 메뉴 {}

protocol 커피직원 {
    func 주문(브랜드메뉴: 메뉴, 결제방식: 결제방식) -> 메뉴
    func 커피제조(브랜드메뉴: 메뉴) -> 메뉴
}

 

이런식일겁니다. 이걸 바탕으로 위에 코드를 수정해본다면

 

import Foundation

protocol 커피메뉴 {}

protocol 커피직원 {
    func 주문(메뉴: 커피메뉴, 결제방식: 결제방식) -> 커피메뉴
    func 커피제조(메뉴: 커피메뉴) -> 커피메뉴
}

enum 스타벅스메뉴: 커피메뉴 {
    case 아이스아메리카노
    case 핑크플라워유스베리티
}

enum 메가커피메뉴: 커피메뉴 {
    case 아이스아메리카노
    case 쿠키프라페
}

enum 투썸메뉴: 커피메뉴 {
    case 흑임자라떼
    case 핫아메리카노
}

enum 결제방식 {
    case 신용카드
    case 현금
    case 애플페이
    case 삼성페이
}

class 스타벅스직원: 커피직원 {
    func 주문(메뉴: 커피메뉴, 결제방식: 결제방식) -> 커피메뉴 {
        print("스타벅스직원 : \(메뉴) 주문받았습니다. \(결제방식)으로 결제하겠습니다.")
        커피제조(메뉴: 메뉴)
        print("스타벅스직원 : 주문하신 \(메뉴) 나왔습니다.")
        return 메뉴
    }
    
    func 커피제조(메뉴: 커피메뉴) -> 커피메뉴 {
        print("스타벅스직원 : \(메뉴) 조리시작")
        print("스타벅스직원 : \(메뉴) 조리완료")
        return 메뉴
    }
}


class 메가커피직원: 커피직원{
    func 주문(메뉴: 커피메뉴, 결제방식: 결제방식) -> 커피메뉴 {
        print("메가커피직원 : \(메뉴) 주문받았습니다. \(결제방식)으로 결제하겠습니다.")
        커피제조(메뉴: 메뉴)
        print("메가커피직원 : 주문하신 \(메뉴) 나왔습니다.")
        return 메뉴
    }
    
    func 커피제조(메뉴: 커피메뉴) -> 커피메뉴 {
        print("메가커피직원 : \(메뉴) 조리시작")
        print("메가커피직원 : \(메뉴) 조리완료")
        return 메뉴
    }
}

class 투썸커피직원: 커피직원{
    func 주문(메뉴: 커피메뉴, 결제방식: 결제방식) -> 커피메뉴 {
        print("투썸커피직원 : \(메뉴) 주문받았습니다. \(결제방식)으로 결제하겠습니다.")
        커피제조(메뉴: 메뉴)
        print("투썸커피직원 : 주문하신 \(메뉴) 나왔습니다.")
        return 메뉴
    }
    
    func 커피제조(메뉴: 커피메뉴) -> 커피메뉴 {
        print("투썸커피직원 : \(메뉴) 조리시작")
        print("투썸커피직원 : \(메뉴) 조리완료")
        return 메뉴
    }
}

class 커피를마시고싶은나 {
    var delegate: 커피직원?
    
    func 커피주문(메뉴: 커피메뉴, 결제방식: 결제방식) {
        print("나 : \(메뉴) 주세요")
        delegate?.주문(메뉴: 메뉴, 결제방식: 결제방식)
        print("나 : 주문한 \(메뉴) 마시기")
    }
}

 

이와 같을겁니다.

 

이러면 몇개의 브랜드나 메뉴가 추가가 되던 저는 주문을 할 수 있습니다.

커피직원 프로토콜을 채택한 직원이라면 말이죠

 

 

왜 프로토콜 프로그래밍을 지향해야하는지 이해하셨을거라 생각합니다.

 

 

이제 델리게이트 패턴을 이해하였으니 우리가 평소에 봐왔던 코드를 다시본다면 무슨의미인지 이제 이해할 수 있겠죠

 

tableView를 대신해서 일을 대신해줄 사람(delegate)은 self 즉 ViewController 구나 !

일을 대신해줄 사람의 타입은 UITableViewDelegate를 채택해야겠죠 ??

그래야만 tableView가 일을 대신해줄 사람이 어떤것들을 하는지 알 수 있으니까요.

 

우리는 애플 공식문서를 보지 않더라도 tableView가 아마 이렇게 생겼을것이라 유추할수 있을겁니다.

 

 

delegate패턴을 사용할때의 유의해야할 점은 누가 무슨일을 하는지 알아야한다는 것입니다.

 

우리가 스타벅스에서는 커피를 주문할 수 있지만 치킨은 주문할수 없고

반대로 치킨집에서 카페라떼를 주문할 수 없는것처럼요

 

 

지금까지 delegate 패턴을 공부해봤습니다. 

Contents

포스팅 주소를 복사했습니다

이 글이 도움이 되었다면 공감 부탁드립니다.