클래스 & 상속의 단점
프로토콜
/*(1)*/
protocol SomeProtocol {
func playPiano() // 구체적인 구현하지 않는다.
}
struct MyStruct: SomeProtocol {
func playPiano() {
// 구체적인 구현
]
}
class MyClass: SomeProtocol {
func playPiano() {
// 구체적인 구현
}
}
/*(2)*/
protocol CanFly {
func fly()
}
class Bird1 {
var isFemale = true
func layEgg() {
if isFemale {
print("새가 알을 낳는다.")
}
}
}
class Eagle1: Bird1, CanFly { // 상속할 클래스를 먼저 쓰고, 뒤에 프로토콜들을 쓴다
// isFemale (저장속성)
// layEgg (메서드)
func fly() { // 구체적인 구현
print("독수리가 하늘로 날아올라 간다")
}
func soar() {
print("공중으로 활공한다.")
}
}
class Penguin1: Bird1 {
// isFemale
// layEgg()
func swim() {
print("물 속을 헤엄칠 수 있다.")
}
}
struct Airplane1: CanFly {
func fly() {
print("비행기가 날아간다")
}
}
struct FlyingMuseam1 {
// (중요) 프로토콜을 타입으로 사용
// -> CanFly 자격증을 딴 class, struct를 인수로 사용할 수 있다.
func flyingDemo(flyingObject: CanFly) {
flyingObject.fly()
}
}
/*(1). 정의*/
protocol MyProtocol {
func doSomething() -> Int
}
class FamilyClass { }
/*(2). 채택*/
class MyClass: FamilyFlass, MyProtocol {
func doSomething() -> Int {
/*(3). 구현*/
return 7
}
}
struct MyStruct: MyProtocol {
func doSomething() -> Int {
return 7
}
}
enum MyEnum: MyProtocol {
func doSomething() -> Int {
return 7
}
}
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" // let으로 선언도 가능
var name: String = "삼성티비"
static var type: String = "리모콘"
/*(1). 정의*/
protocol RandomNumber {
static func reset()
func random() -> Int
mutating func doSomething() // 구조체에서는 원래 저장속성 변경이 안되는데, 변경을 허락해준다는 키워드
// 클래스에서는 그냥 쓰면 된다
}
/*(2). 채택, (3). 구현*/
class Number: RandomNumber {
class func reset() { // static으로 해도 되고 class로 해도 된다
print("다시 세팅")
}
func random() -> Int {
return Int.random(in: 1...100)
}
}
// 1) 정의
protocol Togglable {
// mutating 키워드는 메서드 내의 속성 변경의 의미를 가진다 (당연히 클래스에서도 사용 가능하다)
mutating func toggle()
}
// 2) 채택 / 3) 구현
enum OnOffSwitch: Togglable {
case on
case off
mutating func toggle() { // 구조체와 열거형 모두 "값 타입"이기 때문에 mutating 키워드가 필요하다
switch self { // .on .off
case .off:
self = .on
case .on:
self = .off
}
}
}
var s = OnOffSwitch.off
s.toggle() // on
s.toggle() // off
// 껐다 켰다 하는 메서드를 만들었다
class BigSwitch: Togglable {
var isOn = false // 저장 속성 하나 만들어 놓고
func toggle() { // mutating 키워드 필요없음 (클래스 이기 때문)
isOn = isOn ? false : true // 삼항연산자
}
}
protocol SomeProtocol { // 생성자를 요구사항으로 지정 가능
init(num: Int)
}
// 예제 - 1 ======================
class SomeClass: SomeProtocol {
required init(num: Int) {
// 실제 구현
}
// 편의생성자로 구현 가능 -> 지정생성자 호출해야겠지
required convenience init() {}
}
class SomeSubClass: SomeClass {
// 하위 클래스에서 생성자 구현 안하면 필수 생성자는 자동 상속
// required init(num: Int)
// 만약 생성자를 구현한다면,
init() {
//
}
required init(num: Int) { // 필수 생성자 구현해줘야 한다.
//
}
}
// 예제 - 2 ======================
protocol AProtocol {
init()
}
class ASuperClass {
init() {
// 생성자의 내용 구현
}
}
class ASubClass: ASuperClass, AProtocol {
// AProtocol을 채택함으로 "required" 키워드 필요하고, 상속으로 인한 "override(재정의)" 재정의 키워드도 필요
// 클래스 상속으로 인한 override, 프로토콜로 인한 required
required override init() {
// 생성자의 내용 구현
}
}
실패 가능이 더 크고, 그 속에 실패불가능이 포함된 구조
protocol AProto {
/*(1). 실패가능으로 정의*/
init?(num: Int)
/*(2). 실패 불가능으로 정의*/
init(num: Int)
}
struct AStruct: AProto { // Failable/Non-failable 모두 요구사항을 충족시킴
/*(1)*/
init?(num: Int) {} // 당연히 실패 가능으로 정의 가능
init(num: Int) {} // 실패 불가능으로 정의 가능
init!(num: Int) {} // 이것도 괜찮음
/*(2)*/
//init?(num: Int) {} // 범위가 더 넓기 때문에 얘는 불가능
init(num: Int) {} // 실패 불가능 가능
//init!(num: Int) {} // 거의 비슷한 범위이기 때문에 이것도 괜찮음 -> 실무에서 거의 이럴 일 없음.
}
// 클래스에서 채택 - required 키워드
class AClass: AProto {
required init(num: Int) {}
}
protocol DataList {
subscript(idx: Int) -> Int { get}
}
struct DataStructure: DataList {
/*1*/
subscript(idx: Int) -> Int { // 읽기 전용 서브스크립트로 구현한다면
get { // get만 있으니까 생략 가능
return 0
}
}
/*2*/
subscript(idx: Int) -> Int { // (최소한만 따르면 됨)
get {
return 0
}
set { // 구현은 선택
// 상세구현 생략
}
}
}