자식 클래스는 부모 클래스의 행동과 계속 호환되어아 햡니다
서브타입은 언제나 기반타입으로 교체 할 수 있어야 한다
즉, 부모 클래스의 인스턴스를 사용하는 위치에 자식 클래스의 인스턴스를 대신 사용했을 때 코드가 원래 의도대로 작동해야 한다
리스코프 치환 원칙같은 경우 여러 방식으로 해석 가능한 다른 원칙들과 달리 형식적인 요구사항이 있다
class Animal { }
class Dog: Animal { }
class Bulldog: Dog { }
class ParentClass {
func feed(_ a: Dog) { }
}
class FirstChildClass: ParentClass {
func feed(_ a: Animal) { }
}
class SecondChildClass: ParentClass {
func feed(_ a: Bulldog) { }
}
부모클래스는 Dog 타입에 먹이를 주는 함수가 있다
첫번째 자식클래스에서는 매개변수로 Dog 의 상위클래스인 Animal을 받는다
두번째 자식클래스에서는 매개변수로 Dog 의 하위클래스인 Bulldog을 받는다
만약 부모클래스의 객체 대신 자식클래스의 객체를 전달한다고 생각해보자
첫번째 자식클래스는 모든 동물들에게 먹이를 줄 수 있으므로 클라인트가 전달하는 모든 Dog 에게 먹이를 줄 수 있다
반면 두번째 자식클래스에서는 매개변수를 불독으로만 제한했다
따라서 해당 메서드는 불독 이외의 다른 종의 Dog에는 먹이를 주지 못하며 부모클래스와 호환되지 못한다
class Animal { }
class Dog { }
class Booldog { }
class ParentClass {
func buyDog() -> Dog {
return Dog()
}
}
class FirstChildClass: ParentClass {
func buyDog() -> Animal {
return Animal()
}
}
class SecondChildClass: ParentClass {
func buyDog() -> Booldog {
return Booldog()
}
}
부모클래스에는 buyDog() 이라는 메서드가 있고 Dog을 반환한다
첫번째 자식클래스는 Dog의 하위유형인 Animal 을 반환하고 두번재 자식클래스는 상위유형인 Bulldog 을 반환한다
첫번째 자식클래스는 어떠한 동물이든 다 반환한다
Dog을 위해 설계된 구조에 알 수 없는 다른 동물을 받기때문에 문제가 된다
반면 두번째 자식클래스에서는 Booldog을 반환한다
이는 Dog이니까 아무문제 없다
즉 예외 유형들은 부모 메서드가 이미 던질 수 있는 예외 유형들의 하위유형 혹은 일치해야 합니다
enum SomeError: Error {
case someError
}
class ParentClass {
func doSomething(_ a: String) { }
}
class ChildClass {
func doSomething(_ a: String) throws {
if a > 10 {
throw SomeError.someError
}
}
}
예상치 못한 예외는 앱 전체를 충돌시킬 수 있다
대부분의 현대 프로그래밍 언어들은 위 규칙들이 언어에 내장되어 있어서 해당 규칙들을 위반하는 프로그램은 컴파일 할 수 없다
이제부터 설명하는 규칙들은 컴파일러로 못잡는 규칙들이다
enum SomeError: Error {
case someError(String)
}
class ParentClass {
func doSomething(_ a: Int) throws {
if a < 0 {
throw SomeError.someError("음수이면 안됩니다")
}
}
}
class FirstChildClass: ParentClass {
override func doSomething(_ a: Int) throws {
if a <= 0 {
throw SomeError.someError("0보다 커야합니다")
}
}
}
부모클래스의 doSomething 함수는 파라미터로 받은 숫자가 음수이면 안된다는 조건이 있다
자식클래스에서 doSomething 함수를 재정의하면서 0이면 안된다는 조건이 추가됐다
해당 함수에 음수들이 전달될때 잘 작동하던 클라이언트 코드는 이 자식 클래스 객체와 작업하기 시작하면 문제가 생길 수 있다
부모클래스와 동일한 수준의 조건을 기대하고 사용하는 프로그램 코드에서 예상치 못한 문제가 발생할 수 있기 때문이다
enum SomeError: Error {
case someError(String)
}
class ParentClass {
func doSomething(_ a: Int) throws -> Int {
if a < 0 {
throw SomeError.someError("음수이면 안됩니다")
}
return a
}
}
class ChildClass: ParentClass {
override func doSomething(_ a: Int) throws -> Int {
return a
}
}
부모클래스의 doSomething 함수는 반환할 값이 유효한 값인지 검사하고 있다
자식클래스는 doSomething 함수를 재정의하면서 해당 조건을 제거하여 조건을 약화시켰다
이 역시 음수를 반환할거라고 예상하지 못하는 클라이언트 코드에서는 오작동을 일으킬것이다
불변속성이란 객체가 해당 객체로 이해되기 위해 갖추어야 하는 조건들이다
즉 부모 클래스의 데이터의 값의 조건은 자식 클래스에서도 계속 유지되어야 한다는 것이다
class ParentClass {
var num: Int = .zero
var _num: Int {
get {
return num
}
set {
if newValue >= 0 {
num = newValue
}
}
}
}
class ChildClass: ParentClass {
func doSomething(_ a: Int) {
num = a
}
}
부모클래스의 num 변수는 항상 0 혹은 양수만을 가질 수 있다
그러나 자식클래스의 doSomething 함수에서 아무런 조건없이 num에 값을 할당해주고 있다
그로인해 num 에 음수가 할당될 수 없다는 부모클래스의 불변속성이 깨져버렸다
A가 B를 상속받았으면 B로서도 역할을 할 수 있어야 한다