🌟 상속 구조의 단점
// 상속 구조의 단점
// 예제
class Bird {
var isFemale = true
func layEgg() {
if isFemale {
print("새가 알을 낳는다.")
}
}
func fly() {
print("새가 하늘로 날아간다.")
}
}
class Eagle: Bird {
// isFamale
// layEgg()
// fly()
func soar() {
print("공중으로 치솟아 난다.")
}
}
class Penguin: Bird {
// isFamale
// layEgg()
// fly() // 상속 구조에서는 펭귄이 어쩔 수 없이 날개됨 ⭐️
func swim() {
print("헤엄친다.")
}
}
// struct가 될 수도 없고(클래스로만 구현가능), 무조건 Bird를 상속해야만 함
class Airplane: Bird {
// isFamale
// layEgg() // 상속 구조에서는 비행기가 알을 낳게됨 ⭐️
override func fly() {
print("비행기가 엔진을 사용해서 날아간다")
}
}
// 플라잉 박물관을 만듦
struct FlyingMuseum {
func flyingDemo(flyingObject: Bird) {
flyingObject.fly()
}
}
let myEagle = Eagle()
myEagle.fly()
myEagle.layEgg()
myEagle.soar()
let myPenguin = Penguin()
myPenguin.layEgg()
myPenguin.swim()
myPenguin.fly() // 문제 ===> 펭귄이 날개 됨(무조건적인 멤버 상속의 단점)
let myPlane = Airplane()
myPlane.fly()
myPlane.layEgg() // 문제 ===> 비행기가 알을 낳음
let museum = FlyingMuseum() // 타입 정의 ===> 오직 Bird 클래스 밖에 안됨
museum.flyingDemo(flyingObject: myEagle)
museum.flyingDemo(flyingObject: myPenguin)
museum.flyingDemo(flyingObject: myPlane) // Bird를 상속해야만 사용 가능
🌟 프로토콜의 도입
// "fly()"라는 기능을 따로 분리해 내기
protocol CanFly {
func fly() // 🌟구체적인 구현은 하지 않음 ===> 구체적인 구현은 자격증을 채택한 곳에서
// 위의 메서드를 구현해야된다는 요구사항!
}
class Bird1 {
var isFemale = true
func layEgg() {
if isFemale {
print("새가 알을 낳는다.")
}
}
}
class Eagle1: Bird1, CanFly { // "CanFly" 자격증을 채택
// isFemale
// layEgg()
func fly() {
print("독수리가 하늘로 날라올라 간다.")
}
func soar() {
print("공중으로 활공한다.")
}
}
class Penguin1: Bird1 {
// isFemale
// layEgg()
func swim() {
print("물 속을 헤엄칠 수 있다.")
}
}
// 구조체에서 채택도 가능
struct Airplane1: CanFly {
func fly() {
print("비행기가 날아간다")
}
}
// 박물관을 만듦
struct FlyingMuseum1 {
func flyingDemo(flyingObject: CanFly) { // 중요한 기능 ===> 프로토콜을 타입으로 인식
flyingObject.fly()
}
}
let myEagle1 = Eagle1()
myEagle1.fly()
myEagle1.layEgg()
myEagle1.soar()
let myPenguin1 = Penguin1()
myPenguin1.layEgg()
myPenguin1.swim()
//myPenguin1.fly() // 더이상 펭귄이 날지 않음
let myPlane1 = Airplane1()
//myPlane1.layEgg() // 더이상 비행기가 알을 낳지 않음
myPlane1.fly()
let museum1 = FlyingMuseum1()
museum1.flyingDemo(flyingObject: myEagle1)
//museum1.flyingDemo(flyingObject: myPenguin1) // 더이상 "CanFly"자격증이 없는 펭귄은 날지 못함
museum1.flyingDemo(flyingObject: myPlane1)
protocol RemoteMouse {
var id: String { get } // ===> let 저장속성 / var 저장속성 / 읽기계산속성 / 읽기,쓰기 계산속성
var name: String { get set } // ===> var 저장속성 / 읽기,쓰기 계산속성
static var type: String { get set } // ===> 타입 저장 속성 (static)
// ===> 타입 계산 속성 (class)
}
// 채택하면, (최소한의)요구사항을 정확하게 따라서 구현해야함
// 인스턴스 저장/계산 속성 =========================
struct TV: RemoteMouse {
var id: String = "456"
var name: String = "삼성티비"
static var type: String = "리모콘"
}
let myTV = TV()
myTV.id
myTV.name
TV.type
// 타입 속성 ===================================
// 1) 저장 타입 속성으로 구현
class SmartPhone: RemoteMouse {
var id: String {
return "777"
}
var name: String {
get { "아이폰" }
set { }
}
static var type: String = "리모콘" // 타입 저장 속성은 (상속은 되지만) 재정의 원칙적 불가능
}
// 2) 계산 타입 속성으로 구현
class Ipad: RemoteMouse {
var id: String = "777"
var name: String = "아이패드"
class var type: String { // 타입 계산 속성은 재정의 가능 (class키워드 가능)
get { "리모콘" }
set { }
}
}
// 프로토콜의 타입 취급의 장점 - 1 ⭐️
let electronic: [Remote] = [tv, sbox] // 프로토콜의 형식으로 담겨있음
for item in electronic { // 켜기, 끄기 기능만 사용하니 타입캐스팅을 쓸 필요도 없음 (다만, 프로토콜에 있는 멤버만 사용가능)
item.turnOn()
}
// 프로토콜의 타입 취급의 장점 - 2 ⭐️
func turnOnSomeElectronics(item: Remote) {
item.turnOn()
}
turnOnSomeElectronics(item: tv)
turnOnSomeElectronics(item: sbox)
🌟is / as
// 1) is연산자 ==============================
// 특정타입이 프로토콜을 채택하고 있는지 확인
tv is Remote
sbox is Remote
// 프로토콜 타입으로 저장된 인스턴스가 더 구체적인 타입인지 확인 가능
electronic[0] is TV
electronic[1] is SetTopBox
// 2) as연산자 ==============================
// 업캐스팅(as)
let newBox = sbox as Remote
newBox.turnOn()
newBox.turnOff()
// 다운캐스팅(as?/as!)
let sbox2: SetTopBox? = electronic[1] as? SetTopBox
sbox2?.doNetflix()
//(electronic[1] as? SetTopBox)?.doNetflix()
protocol Remote {
func turnOn()
func turnOff()
}
extension Remote { // (요구사항의 메서드 우선순위 적용 - 프로토콜 메서드 테이블 만듦)
func turnOn() { print("리모콘 켜기") } // 1. (채택)구현시 해당 메서드 2. 기본 메서드
func turnOff() { print("리모콘 끄기") } // 1. (채택)구현시 해당 메서드 2. 기본 메서드
func doAnotherAction() { // (요구사항 메서드 X - 테이블 만들지 않음)
print("리모콘 또 다른 동작") // 타입에 따른 선택 (Direct Dispatch)
}
}
class Ipad: Remote {
func turnOn() { print("아이패드 켜기") }
func doAnotherAction() { print("아이패드 다른 동작") }
}
/**=================================================
[Class Virtual 테이블]
- func turnOn() { print("아이패드 켜기") }
- func turnOff() { print("리모콘 끄기") }
- func doAnotherAction() { print("아이패드 다른 동작") }
====================================================**/
let ipad: Ipad = Ipad()
ipad.turnOn() // 클래스 - V테이블
ipad.turnOff() // 클래스 - V테이블
ipad.doAnotherAction() // 클래스 - V테이블
// 아이패드 켜기
// 리모콘 끄기
// 아이패드 다른 동작
/**=====================================
[Protocol Witness 테이블] - 요구사항
- func turnOn() { print("아이패드 켜기") }
- func turnOff() { print("리모콘 끄기") }
========================================**/
let ipad2: Remote = Ipad()
ipad2.turnOn() // 프로토콜 - W테이블
ipad2.turnOff() // 프로토콜 - W테이블
ipad2.doAnotherAction() // 프로토콜 - Direct (직접 메서드 주소 삽입)
// 아이패드 켜기
// 리모콘 끄기
// 리모콘 또 다른 동작
struct SmartPhone: Remote {
func turnOn() { print("스마트폰 켜기") }
func doAnotherAction() { print("스마트폰 다른 동작") }
}
/**=====================================
[구조체] - 메서드 테이블이 없음
========================================**/
// 본래의 타입으로 인식했을때
var iphone: SmartPhone = SmartPhone()
iphone.turnOn() // 구조체 - Direct (직접 메서드 주소 삽입)
iphone.turnOff() // 구조체 - Direct (직접 메서드 주소 삽입)
iphone.doAnotherAction() // 구조체 - Direct (직접 메서드 주소 삽입)
// 스마트폰 켜기
// 리모콘 끄기
// 스마트폰 다른 동작
/**=====================================
[Protocol Witness 테이블] - 요구사항
- func turnOn() { print("스마트폰 켜기") }
- func turnOff() { print("리모콘 끄기") }
========================================**/
// 프로토콜의 타입으로 인식했을때
var iphone2: Remote = SmartPhone()
iphone2.turnOn() // 프로토콜 - W테이블
iphone2.turnOff() // 프로토콜 - W테이블
iphone2.doAnotherAction() // 프로토콜 - Direct (직접 메서드 주소 삽입)
// 스마트폰 켜기
// 리모콘 끄기
// 리모콘 또 다른 동작