상속, 클래스, 그리고 하위 클래스
상속 개념은 현실 세계의 관점을 프로그래밍으로 가져왔다. 이것은 클래스에 어떤 특성(예를 들어 메서드와 프로퍼티)을 정의할 수 있게 해주었고, 그 클래스를 상속받은 다른 클래스를 생성할 수 있게 해준다. 상속된 클래스는 부모 클래스의 모든 기능을 상속받으며 자신만의 기능을 추가하게 된다.
- 클래스 상속을 통해 클래스 계층 구조를 만든다.
- 계층 구조의 최상위에 있는 클래스를 베이스 클래스 또는 루트 클래스라고 부르며, 상속받은 클래스들을 하위 클래스 또는 자식 클래스라고 부른다.
- 하나의 클래스는 하위 클래스를 얼마든지 가질 수 있다.
- 클래스는 루트 클래스를 상속받을 수도 있다.
- 스위프트의 하위 클래스는 반드시 단 한개의 부모 클래스만 둘 수 있다. 이것은 단일 상속 이라는 개념이다.
스위프트 상속 예제
class BankAccount { var accountBalance: Float var accountNumber: Int init(number: Int, balance: Float) { accountNumber = number accountBalance = balance } func displayBalance() { print("Number \(accountNumber)") print("Current balance is \(accountBalance)") } } - 이것은 다소 단순한 클래스이지만, 계좌번호와 잔고를 저장하기 위해서는 충분하다. 그런데 BankAccount 클래스 뿐만 아니라 저축 계좌로 사용할 클래스가 필요하다고 가정해보자.
- 저축 계좌 역시 계좌 번호와 잔고를 저장해야 할 것이며, 이러한 데이터에 접근할 수 있는 메서드도 필요할 것이다.
- 이를 구현하기 위해 BankAccount 클래스의 모든 기능을 복사해 새로운 클래스를 만들고, 저축 계좌에 필요한 새로운 기능을 넣는 방식이 있다.
- 하지만, 조금 더 효율적인 방법은 BankAccount 클래스의 하위 클래스로 새로운 클래스를 만드는 것이다.
SavingsAccount라고 불릴 BankAccount의 하위 클래스를 생성하기 위해 새로운 클래스를 선언하고, 부모 클래스로 BankAccount를 지정하자.
class SavingsAccount: BankAccount { } 하위 클래스 기능 확장
이제 부모 클래스의 모든 기능을 포함하는 하위 클래스를 만들 수 있게 되었다. 하지만, 실제로 어떻게 되는지 확인하기 위해 저축 계좌 정보를 저장할 수 있도록 하위 클래스를 확장한다.
class SavingsAccount: BankAccount { // 클래스 상속 var interestRate: Float = 0.0 // 하위 클래스 프로퍼티 func calculateInterest() -> Float // 하위 클래스 함수 { return interestRate * accountBalance } } 상속받은 메서드 오버라이딩하기
상속을 사용할 경우, 필요한 작업과 거의 비슷한 메서드를 부모 클래스에서 찾을 수 있을 것이다. 그러나 정확한 기능을 제공하기 위해서는 수정이 필요하다.
이러한 조건에서 할 수 있는 한가지 방법은 상속된 메서드를 무시하고 완전히 새로운 이름의 메서드를 새롭게 만드는 것이다. 하지만, 좀 더 좋은 방법으로는 상속받은 메서드를 오버라이딩하여 하위 클래스 내에 새로운 버전의 메서드를 만드는 것이다.
- 하위 클래스의 오버라이딩 메서드는 오버라이딩되는 부모 클래스 메서드의 매개변수 개수와 타입이 정확하게 일치해야 한다.
- 새롭게 오버라이딩하는 메서드는 반드시 부모 클래스 메서드가 반환하는 타입과 일치해야 한다.
- BankAccount 클래스에는 은행 계좌번호와 현재 잔고를 표시하는 displayBalance라는 이름의 메서드가 있다. 여기에 더불어 하위 클래스인 SavingsAccount에서는 계좌에 할당된 현재 이자율도 출력하고 싶다.
- 이를 위해 override 키워드가 앞에 붙은 displayBalance 메서드의 새로운 버전을 SavingsAccount 클래스에 선언한다.
class SavingsAccount: BankAccount { var interestRate: Float = 0.0 func calculateInterest() -> Float { return interestRate * accountBalance } override func displayBalance() { print("Number \(accountNumber)") print("Current balance is \(accountBalance)") print("Prevailing interest rate is \(interestRate)") } } - 또한, 하위 클래스에서 오버라이드된 상위 클래스의 메서드를 호출할 수도 있다. 예를 들어, 상위 클래스의 displayBalance 메서드는 이자율을 표시하기 전에 계좌번호와 잔고를 출력하도록 호출될 수 있으므로 코드의 중복을 없앨 수 있다.
override func displayBalance() { super.displayBalance() // super: 상위 클래스의 기능 먼저 실행해! (기존 함수) print("Prevailing interest rate is \(interestRate)") // 새로운 기능: 이자율 추가로 출력 } super.displayBalance()가 뭐냐면? -> 상위 클래스가 정의한 함수인 displayBalance()를 먼저 실행해달라는 뜻.
- 이렇게 하는 이유: 코드 중복 제거, 기능 확장(기존 기능은 유지한 채로)
하위 클래스 초기화하기
현재 SavingsAccount 클래스는 다음과 같이 부모 클래스인 BankAccount의 초기화 메서드를 상속하고 있다.
init(number: Int, balance: Float) { accountnumber = number accountBalance = balance } - 이 메서드는 클래스의 계좌 번호와 잔고 모두를 초기화하는데 필요한 과정을 진행한다.
- 하지만, SavingsAccount 클래스는 이자율에 대한 변수가 추가로 필요하다.
- 따라서 SavingsAccount 클래스의 인스턴스가 생성될 때 interestRate 프로퍼티가 초기화되도록 해야 한다.
- SavingsAccount의 init 메서드는 이자율을 초기화하는 작업을 한 다음, 부모 클래스의 init 메서드를 호출하여 모든 변수가 초기화되도록 한다.
class SavingsAccount: BankAccount { var interestRate: Float init(number: int, balance: Float, rate: Float) { interestRate = rate super.init(number: number, balance: balance) } } - 초기화 과정에서 발생할 수 있는 잠재적인 문제를 피하기 위해 상위 클래스의 init 메서드는 항상 하위 클래스의 초기화 작업이 완료된 후에 호출되도록 해야 한다.
Swift에서는 하위 클래스가 초기화될 때 반드시 하위클래스 고유 프로퍼티 먼저 초기화, 그 다음에 super.init(...)으로 상위 클래스 초기화를 호출한다.
- 왜? 객체가 완전히 만들어지기 전에 부모 클래스의 로직이 실행되면 아직 초기화되지 않은 값이 쓰일 수도 있고, 그로 인해 예상치 못한 버그나 크래시가 발생할 수도 있다.
SavingsAccount 클래스 사용하기
이제 SavingsAccount 클래스에 대한 작업이 끝났으니 부모 클래스인 BankAccount를 사용했던 이전의 예제 코드처럼 SavingsAccount 클래스를 사용할 수 있다.
let savings1 = SavingsAccount(number: 12311, balance: 600.00, rate: 0.07) print(savings1.calculateInterest()) savings1.displayBalance() 스위프트 클래스 익스텐션
스위프트 클래스에 새로운 기능을 추가하는 또 다른 방법은 익스텐션을 이용하는 것이다. 익스텐션은 하위 클래스를 생성하거나 참조하지 않고 기존 클래스에 메서드, 생성장, 그리고 연산 프로퍼티와 서브스크립트 등의 기능을 추가하기 위해 사용될 수 있다.
extension Double { // 기존 클래스 이름 var squared: Double { return self * self } var cubed: Double { return self * self * self } } - 익스텐션은 하위 클래스를 사용하지 않고 클래스의 기능을 확장할 수 있는 빠르고 편리한 방식을 제공한다. 하지만, 하위 클래스 역시 익스텐션보다 좋은 장점들을 가지고 있다. 예를 들어, 익스텐션을 이용해서는 클래스에 있는 기존의 기능을 오버라이드 할 수 없으며, 익스텐션에는 저장 프로퍼티를 포함할 수도 없다.