Parent c = new Parent();
Parent p = new Child(); // 정상 작동
LSP 에 따라 위 코드가 문제없이 동작하려면 하위 클래스(Child)가 상위 클래스(Parent)의 행위를 완전히 대체할 수 있도록 설계되어야 한다.
LSP의 핵심은 부모 클래스의 행동 규약을 자식 클래스가 위반하면 안 된다는 것이다.
컴포지션 방법 사용
LSP는 다형성의 원칙을 지키도록 강제하는 규칙으로 볼 수 있다.
컴포지션 방식을 사용할 때, 부모 클래스의 기능을 유연하게 사용할 수 있다.
→ 따라서 LSP 에 따른 객체 지향 관계의 클래스 간의 관계를 표현할 때, 상속보다 컴포지션이 더 적절할 수 있다.
상속 (is-a) : 행동을 정의한 상속
컴포지션 (has-a) : 포함 관계를 정의한 다중 상속
LSP를 위반한 예시
다음은 LSP 위반 예시로 유명한 사각형 넓이 구하기 이다.
Rectangle 클래스
@Getter
@Setter
public class Rectangle {
private int width;
private int height;
public int area() {
return width * height;
}
}
Square 클래스
public class Square extends Rectangle {
@Override
public void setWidth(int width) {
super.setWidth(width);
super.setHeight(width);
}
@Override
public void setHeight(int height) {
super.setWidth(height);
super.setHeight(height);
}
}
LspMain 테스트
public class LspMain {
public static void main(String[] args) {
System.out.println("\n==== 부모(Rectangle) 클래스 ====");
Rectangle rectangle = new Rectangle();
rectangle.setWidth(4);
rectangle.setHeight(5);
System.out.println("넓이 = " + rectangle.area());
System.out.println("\n===== 자식(Square) 으로 대체 ====");
Rectangle square = new Square();
square.setWidth(4);
square.setHeight(5);
System.out.println("넓이 = " + square.area());
System.out.println("\n====== 동일한 결과 값 인가? ======");
System.out.println(rectangle.area() == square.area() );
}
}
출력 결과
==== 부모(Rectangle) 클래스 ====
넓이 = 20
===== 자식(Square) 으로 대체 ====
넓이 = 25
====== 동일한 결과 값 인가? ======
false
문제점
부모 클래스의 행동 규약을 위반한 자식 클래스
Rectangle의 규약: width와 height를 독립적으로 설정
Square의 규약: width와 height가 항상 동일
→ 메서드의 예상 동작이 변경됨 → LSP 위반
실패한 상속 관계
Square는 수학적으로 Rectangle의 특수한 형태지만, 동작 방식이 다르므로 OOP 관점에서는 상속관계가 적절하지 않다.
→ Square 과 Rectangle 을 개별 클래스로 분리 후, 컴포지션 방식으로 리팩토링 해보자
LSP를 준수한 예시
상속 대신 컴포지션 방식을 사용해 LSP를 준수한 코드로 변경했다.
Shape 인터페이스
public interface Shape {
int area();
}
Rectangle 클래스
@Getter
@AllArgsConstructor
public class Rectangle implements Shape {
private int width;
private int height;
@Override
public int area() {
return width * height;
}
}
Square 클래스
@Getter
@AllArgsConstructor
public class Square implements Shape {
private int side;
@Override
public int area() {
return side * side;
}
}
LspMain 테스트
public class LspMain {
public static void main(String[] args) {
System.out.println("\n==== 부모(Rectangle) 클래스 ====");
Shape rectangle = new Rectangle(4, 5);
System.out.println("넓이 = " + rectangle.area());
System.out.println("\n===== 자식(Square) 으로 대체 ====");
Shape square = new Square(3);
System.out.println("넓이 = " + square.area());
System.out.println("\n====== 동일한 결과 값 인가? ======");
System.out.println(rectangle.area() == square.area() );
}
}
출력 결과
==== 부모(Rectangle) 클래스 ====
넓이 = 20
===== 자식(Square) 으로 대체 ====
넓이 = 16
====== 동일한 결과 값 인가? ======
false
리스코프 치환 원칙
LSP 에 따라 위 코드가 문제없이 동작하려면 하위 클래스(Child)가 상위 클래스(Parent)의 행위를 완전히 대체할 수 있도록 설계되어야 한다.
LSP의 핵심은 부모 클래스의 행동 규약을 자식 클래스가 위반하면 안 된다는 것이다.
컴포지션 방법 사용
→ 따라서 LSP 에 따른 객체 지향 관계의 클래스 간의 관계를 표현할 때, 상속보다 컴포지션이 더 적절할 수 있다.
is-a) : 행동을 정의한 상속has-a) : 포함 관계를 정의한 다중 상속LSP를 위반한 예시
다음은 LSP 위반 예시로 유명한 사각형 넓이 구하기 이다.
Rectangle 클래스
Square 클래스
LspMain 테스트
출력 결과
문제점
Rectangle의 규약: width와 height를 독립적으로 설정Square의 규약: width와 height가 항상 동일→ 메서드의 예상 동작이 변경됨 → LSP 위반
Square는 수학적으로 Rectangle의 특수한 형태지만, 동작 방식이 다르므로 OOP 관점에서는 상속관계가 적절하지 않다.
→ Square 과 Rectangle 을 개별 클래스로 분리 후, 컴포지션 방식으로 리팩토링 해보자
LSP를 준수한 예시
상속 대신 컴포지션 방식을 사용해 LSP를 준수한 코드로 변경했다.
Shape 인터페이스
Rectangle 클래스
Square 클래스
LspMain 테스트
출력 결과
결과
⭐발표자 : 이현진님