1 분 소요

LSP?

LSP는 리스코프 치환 원칙(Liskov Substitution Principle) 의 약자로 서브 타입은 언제나 부모타입으로 교체 가능해야함을 뜻한다.

즉 자식 클래스에서 최소한 자신의 부모 클래스에서 가능한 행위의 수행이 보장되어야함을 의미한다.

예를 들어, 변수에 LinkedList 자료형을 담아 사용하다 중간에 전혀 다른 HashSet 자료형으로 바꿔도 Collection의 추상 메서드를 각 클래스에서 인터페이스 구현 규약을 잘 지키도록 implements 되어 있기 때문에 add()의 메서드 동작이 보장받는다.


목적

다형성을 지원하기 위한 원칙

이는 곧 자식 클래스의 구현 원칙, 행동 규약을 의미한다.

상위 클래스 변수로 하위 클래스 인스턴스를 받았을 때,

부모의 메서드를 사용해도 동작이 의도대로만 흘러가도록 구성해야한다는 점이다.

자식 메서드에서 잘못된 오버로딩, 오버라이딩을 해 부모 메서드와 어긋나는 기능을 갖게 된다면

기존 부모 클래스를 사용하는 코드에서 예상치 못한 결과를 야기할 수 있다.


예제
abstract class Animal{
	void speak() {} 
}

class Cat extends Animal{
	void spaek() {
		System.out.println("냐옹");
	}
}
class Dog extends Animal{
	void speak() {
		System.out.println("멍멍");
	}
}
class Fish extends Animal{
	void speak() {
		try {
			throw new Exception("물고기는 말할 수 없음") ; 
		}catch(Exception e) {
			e.printStackTrace();
		}
	}
}

public class LSP {
	public static void main(String[] args) {
		List<Animal> list = new ArrayList<>(); 
		list.add(new Cat()); 
		list.add(new Dog()); 
		list.add(new Fish()); 
		
		for(Animal a : list) {
			a.speak(); // java.lang.Exception: 물고기는 말할 수 없음
		}
	}
}


문제

LSP 원칙에 따르면 speak 메서드 실행 시 Animal 클래스의 설계 목적에 따라

‘각 동물 타입에 맞게 울부짖는 결과’ 를 내야하는데 갑자기 예외를 던진다.


해결

‘말하는 기능’ 자체를 인터페이스로 분리한다

abstract class Animal{}

interface Speakable{
	void speak() ; 
}
class Cat extends Animal implements Speakable{
	public void speak() {
		System.out.println("냐옹");
	}
}
class Dog extends Animal implements Speakable{
	public void speak() {
		System.out.println("멍멍");
	}
}
class Fish extends Animal{}


결론

LSP 원칙의 핵심은 ‘상속’으로, 상속은 IS-A 관계가 있을 경우로만 제한되어야 한다.

그 외의 경우는 ‘합성’을 이용한다.

실제로 하위 클래스에서 상위 클래스의 메서드를 재정의 해야하는 경우는 굉장히 많으므로,

불필요하게 상속 관계를 이용하는것이 아닌 인터페이스로 구현하여 이용한다.


참고 사이트

💠 완벽하게 이해하는 LSP (리스코프 치환 원칙) (tistory.com)


댓글남기기