Protocols 정리본 (1) - 기본 개념

YeongHyeon Jo·2024년 2월 22일
0

Swift

목록 보기
4/6

Class 상속의 단점

  1. 하나의 클래스만 상속이 가능하다. (다중 상속이 불가능하다)
  2. 기본적인 상위클래스의 메모리 구조를 따라갈 수 밖에 없다.
    -> 필요하지 않는 속성과 메서드도 상속됨.
  3. 클래스(Reference Type)에서만 가능하다.

클래스 상속의 제약을 극복하기 위해 프로토콜을 사용

  • 여러 프로토콜을 동시에 채택할 수 있어 클래스 간의 관계를 유연하게 만든다.
  • 프로토콜은 유연하고 확장 가능한 코드를 작성할 수 있다.
  • 프로토콜을 채택할 경우, 클래스는 필요한 기능을 직접 구현해야한다.
    -> 불필요한 상속 구조를 방지하고 캡슐화를 강화
    *캡슐화(객체지향 프로그래밍): 데이터와 해당 데이터를 조작하는 메서드를 함께 묶는 것. 객체의 내부 상태에 대한 직접적인 접근을 제핞나고, 외부에서는 공개된 메서드를 통해서만 객체의 상태를 조작하도록 한다. (내부 구현을 숨겨 외부에 안정적인 인터페이스를 제공)

프로토콜

A protocol defines a blueprint of methods, properties, and other requirements that suit a particular task or piece of functionality.
공식문서

  • 어떤 기능에 적합한 메서드, 프로퍼티 및 기타 요구사항의 청사진(blueprint: 프로토콜이 객체나 형식을 생성하기 위한 기본 설계도나 템플릿이라는 의미)
  • 구조체, 클래스, 열거형은 프로토콜을 채택해서 프로토콜에 정의된 요구사항의 실제 구현을 제공한다. (이 때, 프로토콜은 정의를 하고 제시를 할 뿐 스스로 기능을 구현하지는 않는다.)
  • 앨런강의 수강 후: 자격증이라는 표현을 사용해서 배우게되었다. 자격증을 따게되면서(채택) 할 수 있는 일을 직접 결정(프로토콜에서 정의하고 제시한 일을 직접 기능 구현)할 수 있는 과정. 자격증은 누구나 딸 수 있다.(타입 사용이 허용되는 모든 곳에 사용 가능. ex. 파라미터 타입, 리턴 타입, 프로퍼티 타입, 딕셔너리의 원소타입)

프로토콜의 기본 문법

  1. 정의
    프로토콜을 생성하여 필요한 요구사항만을 담는다.
protocol SomeProtocol {     // 요구사항을 정의 (자격증의 필수 능력만 정의)
    func playPiano()
}
  1. 채택
    프로토콜을 채택한다. (클래스, 구조체 열거형에서 채택가능)
// 구조체에서 채택
struct MyStruct: SomeProtocol {       // 이제 자격증의 능력이 생긴 것임
    func playPiano() {
        // 구체적인 구현
    }
}

// 클래스에서 채택
class MyClass: SomeProtocol {        // 이제 자격증의 능력이 생긴 것임
    func playPiano() {
        // 구체적인 구현
    }
}

// 만약에 상속이 있는 클래스에서 채택을 할 경우
// 상속하려는 클래스 Person을 먼저 선언하고 다음에 채택하려는 프로토코로을 선언한다.
class Student: Person, SomeProtocol { 
    func playPiano() {
    }
}
  1. 구현
    프로토콜에서 요구하는 사항(속성, 메서드)을 직접 구현한다.
class MyClass: SomeProtocol {
    func playPiano() {
    	// protocol에서 요구한 playPiano() 메서드 동작을 직접 구현
    	print("피아노를 친다")
    }
}

프로토콜을 왜 필요한가? - 클래스와 상속의 단점

위에서 작성한 부분에 대한 예시를 들어보려고 한다.
클래스 상속을 하게되면 무조건적으로 속성과 메서드를 상속하게 된다. 그에 따라 생기는 예시를 들어본다.

class Bird {
    var isFemale = true
    func layEgg() {
        if isFemale {
            print("새가 알을 낳는다.")
        }
    }
    func fly() {
        print("새가 하늘로 날아간다.")
    }
}

Bird라는 클래스를 생성하였다. 이제 이를 상속하는 클래스들을 만들어본다.

class Eagle: Bird {
    // isFamale
    // layEgg()
    // fly()
    func soar() {
        print("공중으로 치솟아 난다.")
    }
}

Eagle이라는 클래스는 Bird를 상속받고 있다. 그리하여 Bird에 있는 속성과 메서드를 그대로 가지고 오게된다.

class Penguin: Bird {
    // isFamale
    // layEgg()
    // fly()       
    func swim() {
        print("헤엄친다.")
    }
}

Penguin이라는 클래스는 Bird를 상속받고 있다. 이렇게되는 경우 펭귄은 날 수 없지만 Bird라는 클래스를 상속받았기 때문에 날 수 밖에 없다.

class Penguin: Bird {
    // isFamale
    // layEgg()
	override func fly() {
        print("펭귄은 날 수 없다...")
    }       
    func swim() {
        print("헤엄친다.")
    }
}

오버라이드를 통해서 상속 받은 fly()메서드를 재정의할 수 있지만, 필요하지 않은 fly()메서드를 굳이 재정의까지하면서 필요한가?라는 의문이 들게된다.
그렇다면 여기에서 fly()라는 메서드를 필요할 때만 사용하기 위해서 프로토콜을 도입해보기러한다.

프로토콜을 채택해보자!

protocol CanFly {
    func fly()
}

CanFly라는 프로토콜을 만들고 fly() 메서드를 요구사항으로 구현한다. 단, 여기에서는 구체적으로 구현하지 않는다.

class Bird {
    var isFemale = true
    func layEgg() {
        if isFemale {
            print("새가 알을 낳는다.")
        }
    }
}

기존에 있던 fly() 메서드는 프로토콜로 구현하기 때문에 해당 클래스에서 제외한다.

class Eagle: Bird, CanFly {
    // isFamale
    // layEgg()
    func fly() {
        print("독수리가 하늘로 날라올라 간다.")
    }
    func soar() {
        print("공중으로 치솟아 난다.")
    }
}

class Penguin: Bird {
    // isFemale
    // layEgg()
    
    func swim() {
        print("물 속을 헤엄칠 수 있다.")
    }
}

Eagle Class에서는 fly()라는 메서드가 필요하여 프로토콜을 채택하였지만, Penguin클래스에서는 필요하지 않기 때문에 프로토콜을 채택하지 않았다.
이를 보면 결과적으로 똑같은 Bird라는 Class를 상속받지만, fly라는 메서드는 CanFly 프로토콜을 채택하여 사용하게 설정하였다.

내가 생각하기론 프로토콜은 다음과 같다.

프로토콜은 요구사항을 정의해놓고 해당 요구사항을 사용하고 싶은 클래스나 구조체, 열거형의 경우 프로토콜을 채택하여 요구사항을 가지고 해당 기능을 자세하게 구현하는 것이다!

profile
my name is hyeon

0개의 댓글