DADAHAE's Log
비싼 장난감 가지고 노는 중 (❁´▽`❁)*✲゚*
[TIL] 22-09-28: Swift (Inheritance, Subclassing, Extension)
2022-09-28 수요일
3주차

 

 

1교시, 2교시

어제자 복습

어제 했던 객체지향 프로그래밍을 복습하기 위해 BankAccount 코드를 다시 한번 더 써보는 중이다.

 

🦁 내부 문법을 정말 잘 모르겠다! 어떡하지 나 개발 못하는 걸까..?
NO, 다 외우려고 들지말자. 익숙해지는 것이 중요하다.

 

 



 

 

3교시, 4교시, 5교시, 6교시

Swift

서브클래싱과 익스텐션

목표

  • 앞서 배운 것
    • 객체지향 프로그래밍의 개념
    • 새로운 클래스를 생성하고 작업하는 방법
  • 이번에 배울 것
    • 상속
    • 서브 클래싱
    • 익스텐션→ 유용하다. 편하다!

 

 

 


 

✔️ Inheritance, 상속

 

정의

예전에는 부모-자식 관계로 설명을 많이 했는데 요즈음엔 super- sub로 설명을 더 많이 한다.

상속 → 현실 세계의 관점을 프로그래밍으로 가져온 것
  • 클래스에 어떤 특성(예. 메서드와 프로퍼티)을 정의할 수 있고, 그 클래스를 상속받은 다른 클래스를 생성할 수 있게 해준다.
  • 상속된 클래스(sub)상속한 클래스(super)의 모든 기능을 상속받고, 자신만의 기능을 추가하게 된다.
  • 계층구조(class hierarchy)
    • base class, child class, sub class
    • prarent class, super class

 

 

상속관계 만들기

  • 밖(클래스 밖)에서는 BankAccount라는 타입으로 부를 수 있다.
class BankAccount {
	var accountBalane: Float
	var accountNumber: Int
	
	init(number: Int, balance: Float) {
		accountNumber = number
		accountBalance = balance
	}

	func displayBalance() {
		print("Number \\(accountBalance)")
		print("Current balance is \\(accountNumber)")
	}
}

BankAccount를 상속받는 저축계좌로 사용할 SavingsAccount 계좌를 만들어보자.

  • BankAccount의 내용을 모두 복사해서 SavingAccount 클래스에 넣고, 필요한 기능을 넣으면 완성이 된다.
  • 근데 효율적이지 못하다. 이러면 BankAccount의 내용이 바뀌었을 때 일일이 또 바꾸어줘야 한다. 그리고 아주 많은 서브클래스가 있다면… 사람의 실수가 발생할 가능성이 높아진다.

 

BankAccount의 내용을 복사하여 만들 수도 있지만, SavingsAccount를 BankAccount의 하위 클래스로 만드는 것이 더욱 효율적이다.

  • BankAccount를 상속받은 SavingsAccount는 BankAccount의 모든 기능을 상속받고, 필요한 기능을 추가하여 확장할 수 있다.

 

그럼 한번 상속관계로 만들어보자.

class SavingsAccount: BankAccount {
	
}

프로토콜을 채택할 때도 위 코드처럼 썼다! 클래스명 옆에 상속받을 클래스명을 적어준다.

아직 SavingAccount 내부에 어떠한 인스턴스 변수나 메서드를 추가하지 않았지만, SavingAccount 클래스는 BankAccount 클래스의 모든 메서드, 프로퍼티를 상속받았음을 알 수 있다.

 

  • protocol
    • 이런거 만들어줘! 내부의 구현된 코드는 없음. 채택한 프로토콜대로 기능을 구현해주어야 한다.
  • super class
    • 내부의 구현된 코드가 있다. 필요한 것만 추가적으로 만들어주면 된다.

 

 

 

✔️ Subclassing, 서브 클래싱

Subclassing is the act of basing a new class on an existing class.

 

하위 클래스의 기능 확장하기

하위 클래스에 필요한 기능을 따로 추가해준다.

  • 슈퍼 클래스의 프로퍼티, 메소드는 다시 적을 필요가 없다. 상속받았으므로 그냥 사용할 수 있다. (프로토콜이었다면 구현부를 작성해줘야 함)

 

BankAccount 클래스를 상속받은 하위 클래스 SavingAccount에 프로퍼티와 메서드를 추가하였다.

class SavingsAccount: BankAccount {

	var interestRate: Float = 0.0
	
	func calculateInterest() -> Float {
		return interestRate * accountBalance
	}
}

 

Overriding

A subclass can provide its own custom implementation of an instance method, type method, instance property, type property, or subscript that it would otherwise inherit from a superclass.

상속받은 메서드를 새롭게 구현하기

상속을 사용하면 슈퍼 클래스에서 사용하는 메서드를 그대로 사용할 수 있다.

그러나 서브 클래스에서 더 정확한 기능을 제공하기 위해 수정이 필요할 수 있다. 그럼 슈퍼 클래스에서 사용하는 기능을 무시하고 새로 만들어야 할까? 그래도 된다. 하지만… 더 좋은 방법이 있다.

상속 받은 메서드오버라이드(override)하여 하위 클래스내에서 새로운 버전의 메서드를 만드는 것이다.

 

  • 오버라이딩을 할 때 지켜야 할 규칙
    • 매개변수, 타입 등 모양이 완전히 일치해야 한다.
    • 반환 타입도 같아야 한다.
    → 함수 타입이 같아야 한다.

 

예시)

class SavingsAccount: BankAccount {

	var interestRate: Float = 0.0
	
	func calculateInterest() -> Float {
		return interestRate * accountBalance
	}

	override func displayBalance() {    // 부모 클래스에 있는 displayBalance 함수를 override.
		print("Number \\(accountNumber)")
		print("Current balance is \\(accountBalance)")
		print("Prevailing interest rate is \\(interestRate)")
	}
}
  • override 키워드가 앞에 붙은 displayBalance 메서드의 새로운 버전을 SavingsAccount 클래스에 선언한다.

 

그런데 메서드를 override 하면, 슈퍼클래스의 메서드에서 썼던 내용도 다 지워진다. 어카지? 난 그 메서드 내용도 쓰고 싶은데!

  • super라는 키워드를 사용하면 super 클래스에서 썼던 내용을 가져올 수 있다.
    • 코드의 중복을 없앨 수 있다~
super.displayBalance()

 

실제 구현부에 넣어보면 아래와 같다.

class SavingsAccount: BankAccount {

	var interestRate: Float = 0.0
	
	func calculateInterest() -> Float {
		return interestRate * accountBalance
	}

	override func displayBalance() {  
		super.displayBalance()  // 부모 클래스의 displayBalance 함수 호출
		print("Prevailing interest rate is \\(interestRate)")
	}
}

 

 

하위 클래스 초기화하기

슈퍼 클래스의 초기화함수를 쓸 수 있다.

init은 func 키워드가 없다. 그래서 오버라이딩이라고 일컫지도 않는다…!

  • 단, 하위 클래스의 init 매개변수가 상위 클래스의 init 매개변수와 같으면 override 키워듣 붙여줘야 한다.
class BankAccount {
	...
	init(number: Int, balance: Float) {
		accountNumber = number
		accountBalance = balance
	}
	...
}

class SavingsAccount: BankAccount {
	...
	var interestRate: Float

	init(number: Int, balance: Float, rate: Float) {
		interestRate = rate
		super.init(number: number, balance: balance)
	}
	...
}
  • 매개변수가 추가되면 override라고 하지 않는다.
  • 초기화 과정에서 발생할 수 있는 잠재적인 문제를 피하기 위해 상위 클래스의 init 메서드는 항상 하위 클래스의 초기화 작업이 완료된 후에 호출되도록 해야 한다.
  • 아니어도 작동은 되지만… 습관적으로 위와 같이 진행하자.

 

아래와 같이 코드를 쓸 수 있다.

let savings = SavingAccount(number: 12311, balance: 600.00, rate: 0.7)

print(savings.calcurateInterest())
savings1.displayBalance()

 

 

✔️ Extension, 익스텐션

Extensions add new functionality to an existing class, structure, enumeration, or protocol type.

필요한 기능이 있는데 상위 클래스에 없으면 하위 클래스를 또 상속받아서 매번 기능을 만들어줘야 하나? 매번 상속을 해줘야 하나? 노놉.

익스텐션을 사용하자. 익스텐션은 메서드만 가능하고 프로퍼티는 불가능하다. (연산o, 저장x)

 

Swift 클래스에 새로운 기능을 추가하는 또 다른 방법은 extension을 사용하는 것이다.
  • 익스텐션은 하위 클래스를 생성하거나 참조하지 않고 기존 클래스에 메서드, 초기화, 그리고 연산 프로퍼티와 서브스크립트 등의 기능을 추가하기 위해 사용될 수 있다.
This includes the ability to extend types for which you don’t have access to the original source code (known as retroactive modeling).

 

extension ClassName {
	// 새로운 기능을 여기에 추가한다.
}

 

어? 프로퍼티는 안된다매.

Nono. 정확하게는 저장 프로퍼티가 안된다. 연산 프로퍼티는 익스텐션으로 추가가 가능하다.

  • Swift 언어와 iOS SDK 프레임워크에 내장된 클래스에 기능을 추가할 때 익스텐션을 이용하면 매우 효과적일 수 있다.

 

예제) 표준 Double 클래스에 제곱값을 반환하는 프로퍼티와 세제곱 값을 반환하는 프로퍼티를 추가하고자 한다고 하자.

이 기능은 다음의 익스텐션 선언부를 이용하여 추가할 수 있다.

extension Double {
	var squared: Double {   // getter
		return self * self
	}
	
	var cubed: Double {
		return self * self * self  // getter
	}
}

여기서 self는 숫자 그 자체 값이라고 보면 된다.

 

Double 클래스에 2개의 새로운 연산 프로퍼티를 가지도록 확장했다. 이제 아래와 같이 사용할 수 있다!

let myValue: Double = 3.0
print(myValue.squared)

// 9.0
  • 주목할 점 : myValue 상수를 선언할 때 Double형이 되도록 선언하고 익스텐션 프로퍼티를 사용했다는 것이다.

 

실제로 프로퍼티는 하위 클래스를 사용하는 것이 아니라 익스텐션으로 추가한 것이므로, Double 값에서 이 프로퍼티에 직접 접근할 수 있다.

let myValue: Double = 3.0
print(myValue.squared)

print(3.0.squared)
print(6.0.squared)

근데 이렇게 하면 소수점과 dot annotation이 헷갈리니까 주의해야한다.

String에서도 이런식으로 사용할 수 있다. 근데 헷갈리니까 머..

 

 

 

📔 요약

  • 상속
    • 새로운 클래스가 기존의 클래스로부터 파생되어 새로운 기능이 추가되도록 하는 방법
    • OOP에서 객체의 재사용성 개념을 확장시켜 준다.
    • 기존 클래스를 새로운 하위 클래스의 기본형처럼 사용할 수 있게 해준다. (하위 클래스에 새로운 기능을 추가하지 않을 경우)
  • 익스텐션
    • 하위 클래스를 생성하지 않고도 기존의 클래스에 기능을 추가할 수 있는 방법 제공

 

 


 

 

남들이 만들어둔 좋은 오픈소스, 코드가 있다면 그것을 활용하는 것이 좋다.

가져다 쓸 수 있으면 쓸 수 있다. 그러나 어떻게 돌아가는지는 알아야겠징? 쿠쿡. 그러다가 오픈소스 오류잡고, 문서 번역하고.. 어쩌구 하다가 취업될수도? 쿠쿠쿡.

 

 


 

✏️ 적용해보기

 

계좌 정보를 담는 클래스를 만들어보자.

// 계좌 정보를 담는 클래스를 만들어라
class BankAccount {

  // 프로퍼티
  // 1. 은행 계좌 번호
  var accountNumber: Int = 0

  // 2. 계좌에 남은 잔고
  var accountBalance: Float = 0.0

  // 3. 수수료
  let fees: Float = 5.00

  // 4.현재의 잔액에서 수수료를 뺀 급액
  // 연산 프로퍼티 - 밖에서 보면 프로퍼티지만 안에서는 get과 set의 메서드(함수)들
  var balanceLessFees: Float {
    get {
      return accountBalance - fees
    }
    set {
      accountBalance = newValue + fees
    }
  }

  // 메서드 - method

  // 초기화 - 계좌 번호와 잔고를 받아서 시작
  init(number: Int, balance: Float) { // (Int, Float) -> Void
    accountNumber = number
    accountBalance = balance
  }

  deinit {
    print("인스턴스가 제거됩니다")
  }

  // 계좌 정보를 출력하기
  func displayBalance() {
    print("Number \\(accountNumber)")
    print("Current balance is \\(accountBalance)")
    print("Current balanceLessFees is \\(balanceLessFees)")
  }

  // BankAccount가 최대로 받을 수 있는 금액
  class func getMaxBalance() -> Float {
    return 100000.00
  }
}

class SavingsAccount: BankAccount {
  // 이자율 프로퍼티 추가
  var interestRate: Float = 0.0254

  // 초기화 - 계좌 번호와 잔고를 받아서 시작
  // 만약 Sub 클래스 어디에도 초기화 함수(init) 없으면 Super 클래스의 init을 끌어와 사용

  // Super 클래스의 초기화 함수와 타입이 같기 때문에 override 적음
  override init(number: Int, balance: Float) { // (Int, Float) -> Void
    interestRate = 0.3
    super.init(number: number, balance: balance)
  }

  // Super 클래스에는 없던 함수 타입의 초기화 함수이기 때문에 override 안써줌
  init(number: Int, balance: Float, rate: Float) { // (Int, Float, Float) -> Void
    interestRate = rate
    super.init(number: number, balance: balance)
  }

  // 이자 구하는 메소드-함수 추가
  func calculateInterest() -> Float {
    return interestRate * accountBalance
  }

  // 계좌 정보를 출력하기
  override func displayBalance() {  // () -> Void
    // Super 클래스인 BankAccount의 내용과 겹치니까
    // print("Number \\(accountNumber)")
    // print("Current balance is \\(accountBalance)")
    // print("Current balanceLessFees is \\(balanceLessFees)")
    super.displayBalance()

    print("Current interestRate is \\(interestRate)")
  }
}

let pocketMoney: Float = 124.0

// 우리가 입금할 금액이 어떤계좌가 만들어지든 입금 가능한 금액일 때에만...
if pocketMoney < SavingsAccount.getMaxBalance()  {

  // BankAccount 타입의 인스턴스(객체) 생성
  let account: SavingsAccount = SavingsAccount(number: 12312312345, balance: pocketMoney)

  print("이자: \\(account.calculateInterest())")

  account.displayBalance()
} else {
  print("금액이 계좌의 한도를 초과했습니다")
}

 

 

 


 

7교시, 8교시

 

회고조안에서 조원들과 함께 Playground 해결하기 - 실전편

  Comments,     Trackbacks