✅ 교체하다 : 자식 클래스는 최소한 자신의 부모 클래스에서 가능한 행위는 수행이 보장되어야 한다는 의미
import java.util.Collection;
import java.util.HashSet;
import java.util.LinkedList;
public class Main {
public static void main(String[] args) {
// Collection 인터페이스 타입으로 변수 선언
Collection<Integer> data = new LinkedList<>();
data.add(1);
data.add(2);
// 중간에 전혀 다른 자료형 클래스를 할당해도 호환됨
data = new HashSet<>();
// 인터페이스 구현 구조가 잘 잡혀있기 때문에 add 메소드 동작이 각기 자료형에 맞게 보장됨
data.add(4);
data.add(5);
for (Integer value : data) {
System.out.print(value + " "); // 4 5
}
}
}
리스코프 치환 원칙(LSP)은 다형성을 지원하기 위한 원칙이다!
✅ 행동 규약을 위반하다 : 자식 클래스가 오버라이딩을 할 때, 잘못되게 재정의하면 리스코프 치환 원칙을 위배할 수 있다는 의미이다.
open class Animal {
open var speed = 100
open fun go(distance: Int): Int {
return speed * distance
}
}
class Eagle : Animal() {
// 오류 발생 : 부모 클래스의 행동 규약을 어김
override fun go(distance: Int, flying: Boolean): String {
return if (flying) {
"$distance 만큼 날아서 갔습니다."
} else {
"$distance 만큼 걸어서 갔습니다."
}
}
}
fun main() {
val eagle: Animal = Eagle()
(eagle as Eagle).go(10, true)
}
open class NaturalType(animal: Animal) {
// 생성자로 동물 이름이 들어오면, 정규표현식으로 매칭된 동물 타입을 설정한다.
val type = when (animal) {
is Cat -> "포유류"
else -> // ...
}
fun print(): String {
return "이 동물의 종류는 $type 입니다."
}
}
open class Animal {
open fun getType(): NaturalType {
return NaturalType(this)
}
}
class Cat : Animal()
fun main() {
val cat = Cat()
val result = cat.getType().print()
println(result) // 이 동물의 종류는 포유류 입니다.
}
open class NaturalType {
open fun getType(): String {
return //...
}
}
class CatNaturalType : NaturalType() {
override fun getType(): String {
return "포유류"
}
}
open class Animal {
open fun getType(): NaturalType {
return NaturalType()
}
}
class Cat : Animal() {
override fun getType(): NaturalType {
return CatNaturalType()
}
}
fun main() {
val cat = Cat()
val result = cat.getType().getType()
println("이 동물의 종류는 $result 입니다.")
}
class Cat : Animal() {
override fun getType(): NaturalType? {
return null
}
fun getName(): String {
return "이 동물의 종류는 포유류 입니다."
}
}
fun main() {
val cat = Cat()
val result = cat.getType().print()
println(result) // Error
}
abstract class Animal {
open fun speak() {}
}
class Cat : Animal() {
override fun speak() {
println("냐옹")
}
}
class Dog : Animal() {
override fun speak() {
println("멍멍")
}
}
class Fish : Animal() {
override fun speak() {
try {
throw Exception("물고기는 말할 수 없음")
} catch (e: Exception) {
e.printStackTrace()
}
}
}
✅ 스펙 문서 : 무엇을, 왜 만들어야 하는지에 대한 명확한 결정사항을 모아놓은 문서
val list = mutableListOf<Animal>()
list.add(Cat())
list.add(Dog())
list.add(Fish())
for (a in list) {
a.speak()
}
리스코프 치환 원칙(LSP)은 협업하는 개발자 사이의 신뢰를 위한 원칙이기도 하다.
abstract class Animal
interface Speakable {
fun speak()
}
class Cat : Animal(), Speakable {
override fun speak() {
println("냐옹")
}
}
class Dog : Animal(), Speakable {
override fun speak() {
println("멍멍")
}
}
class Fish : Animal()
리스코프 치환 원칙 : 다형성의 특징을 이용하기 위해 상위 클래스 타입으로 객체를 선언하여 하위 클래스의 인스턴스를 받으면, 업캐스팅된 상태에서 부모의 메서드를 사용해도 동작이 의도대로만 흘러가도록 구성하면 되는 것이다.
is-a 관계
가 있을 경우로만 제한되어야 한다.