SOLID - L: 리스코프 치환 원칙 (Liskov Substitution Principle)

2025. 6. 15. 15:44·Architecture & Design

객체지향 프로그래밍(OOP)에서는 더 나은 코드 구조와 유지 보수를 위해 SOLID 원칙을 따른다.

SOLID 원칙의 세 번째, L - 리스코프 치환 원칙(LSP) 을 알아보자.

“자식 클래스는 언제나 부모 클래스의 역할을 대체할 수 있어야 한다.”


📌 리스코프 치환 원칙(LSP)이란?

Liskov Substitution Principle

“하위 클래스는 상위 클래스로 교체해도 프로그램의 정확성이 유지되어야 한다.”

즉, 하위 타입은 상위 타입을 대체할 수 있어야 한다는 의미이다.

상속받은 자식 클래스는 부모 클래스의 기능을 대체하거나 확장할 수 있어야지, 변경하거나 위반해서는 안 된다.


💡 핵심
  • 서브클래스가 부모 클래스의 동작을 변경해서는 안 된다.
  • 하위 클래스가 상위 클래스의 규약(계약)을 위반하면 LSP 위반이다.
  • "is-a 관계"가 성립해야 하며, 상속을 잘못 사용하면 오히려 LSP를 어기게 된다.

Java의 컬렉션 프레임워크 (Collection Framework)

LSP의 원칙을 잘 적용한 예제이다.

https://levelup.gitconnected.com/java-collections-framework-class-hierarchy-latest-2024-51f9154f1f57

만약 변수를 `LinkedList`를 쓰다가, `HashSet`으로 자료형을 바꿔도 `add()`나 `remove()` 의 메소드의 동작을 보장받을 수 있다. 이렇게 하기 위해서는 `Collection`이라는 인터페이스 타입으로 변수를 선언하여 할당받으면 된다.

인터페이스 `Collection`의 추상 메서드를 각기 하위 자료형 클래스에 `implements` 하여 인터페이스 구현 규약을 지키도록 미리 설계되어있기 때문이다.

void testData() {
	// Collection 인터페이스 타입으로 변수 선언
    Collection data = new LinkedList();
    data = new HashSet(); // 중간에 전혀 다른 자료형 클래스를 할당해도 호환됨
    
    modify(data); // 메소드 실행
}

void modify(Collection data){
    list.add(1); // 인터페이스 구현 구조가 잘 잡혀있기 때문에 add 메소드 동작이 각기 자료형에 맞게 보장됨
    // ...
}

“리스코프 치환 원칙”은 어려워보이지만, 자바를 쓰면서 사용한 다형성을 지키기 위한 원칙이라고 볼 수 있다.


📝 LSP 예제

“하위 클래스는 상위 클래스로 교체해도 프로그램의 정확성이 유지되어야 한다.”

예시1. 사각형 vs 정사각형

❌ LSP를 위반한 예시

class Rectangle {
    protected int width;
    protected int height;

    public void setWidth(int w) { width = w; }
    public void setHeight(int h) { height = h; }

    public int getArea() { return width * height; }
}
class Square extends Rectangle {
    @Override
    public void setWidth(int w) {
        width = w;
        height = w;
    }

    @Override
    public void setHeight(int h) {
        width = h;
        height = h;
    }
}
  • 위 코드처럼 `Rectangle` 객체를 상속 받은 `Square` 클래스에서는 정사각형의 너비와 높이가 같다는 특징을 구현했다.
  • 정사각형은 직사각형 범주에 포함되므로 정상적으로 동작해야 한다. 이떄 리스코프 치환 원칙은 부모 객체를 호출하는 동작에서 자식 객체가 부모 객체를 완전히 대체 할 수 있다는 원칙이었다.
Rectangle r = new Square();
r.setWidth(5);
r.setHeight(10);
System.out.println(r.getArea());  // 기대값: 50, 실제값: 100
  • 기대한 값인 50이 아닌, 100이 출력된다.
  • 정사각형의 넓이는 잘 구현되었으나 `Rectangle` 클래스의 동작과 그를 상속 받은 `Square` 클래스의 동작이 전혀 다르기 때문이다.
  • 이는 정사각형이 직사각형을 상속 받는 것이 올바른 상속 관계가 아니라는 것을 의미한다. 즉, 자식 객체가 부모 객체의 역할을 완전히 대체하지 못한다는 의미이다.
  • 때문에 이 코드는 리스코프 치환원칙을 위배한다.

 

✅ LSP를 지킨 예시 (인터페이스 분리)

public class Shape {

    public int width;
    public int height;

    // 너비 반환, Width Getter
    public int getWidth() {
        return width;
    }
    // 너비 할당, Width Setter
    public void setWidth(int width) {
        this.width = width;
    }

    // 높이 반환, Height Getter
    public int getHeight() {
        return height;
    }
    // 높이 할당, Height Setter
    public void setHeight(int height) {
        this.height = height;
    }

    // 사각형 넓이 반환
    public int getArea() {
        return width * height;
    }
}
//직사각형 클래스
public class Rectangle extends Shape {

    public Rectangle(int width, int height) {
        setWidth(width);
        setHeight(height);
    }

}

//정사각형 클래스
public class Square extends Shape{
    
    public Square(int length) {
        setWidth(length);
        setHeight(length);
    }
    
}
public class Main {
    public static void main(String[] args) {
    
        Shape rectangle = new Rectangle(10, 5);
        Shape square = new Square(5);
        
        System.out.println(rectangle.getArea());
        System.out.println(square.getArea());
    }
}
  • 더이상 `Rectangle` 객체와 `Square` 객체는 상속 관계가 아니므로, 리스코프 치환 원칙을 준수한다.

 

예시 2: Bird → Ostrich (날 수 없는 새)

❌ LSP를 위반한 예시

class Bird {
    public void fly() {
        System.out.println("Flying...");
    }
}

class Ostrich extends Bird {
    @Override
    public void fly() {
        throw new UnsupportedOperationException("Ostrich can't fly");
    }
}
Bird bird = new Ostrich();
bird.fly();  // 런타임 예외 발생
  • `Bird`는 모두 날 수 있다(`fly`)는 전제를 깔고 있다.
  • `Ostrich`는 `Bird`지만 날지 못하는 새로 예외가 발생한다. → 부모 클래스의 계약(Contract)을 깨뜨림
  • 이로 인해 프로그램이 `Bird`에 기대하던 행동을 할 수 없게 됨 → LSP 위반

 

✅ LSP를 지킨 예시 (인터페이스 분리)

interface Bird {
    void layEgg();
}

interface FlyingBird extends Bird {
    void fly();
}

class Ostrich implements Bird {
    public void layEgg() { ... }
}

class Sparrow implements FlyingBird {
    public void layEgg() { ... }
    public void fly() { ... }
}
  • 알을 낳는 새 `Bird`를 `FlyingBird`가 상속
  • `Bird`는 이제 날 수 있다고 가정하지 않고, 알을 낳는 것만 가정한다.
  • `Ostrich`는 `Bird`를 상속하기에 날지 않는다는 것을 알 수 있다.
  • `FlyingBird` 인터페이스를 구현한 새만 `fly()`를 사용할 수 있다.
  • 타입 안전성 보장 → 리스코프 치환 원칙 준수

🧠 LSP 원칙 적용 주의점

리스코프 치환 원칙의 핵심은 상속(Inheritance)이다.

하지만 여기서 주의할 점은, 객체지향 프로그래밍에서 상속은 기반 클래스와 서브 클래스 사이에 is-a 관계가 있을 경우로만 하도록 제한되어야 한다!

그 외의 경우 (has-a)는 Composition(합성)을 이용하도록 권고되어있다.


📘 참고자료

  • 코드잇 SB 강의 자료
  • https://inpa.tistory.com/entry/OOP-💠-아주-쉽게-이해하는-LSP-리스코프-치환-원칙
  • https://velog.io/@harinnnnn/OOP-객체지향-5대-원칙SOLID-리스코프-치환-원칙-LSP
  • https://levelup.gitconnected.com/java-collections-framework-class-hierarchy-latest-2024-51f9154f1f57
 

Java Collections Framework — Class Hierarchy

Know everything that can be asked on the Collection Hierarchy in the Java Interviews

levelup.gitconnected.com

 

[OOP] 객체지향 5대 원칙(SOLID) - 리스코프 치환 원칙 (LSP)

이번 글에서는 객체지향의 5대 원칙 중, 리스코프 치환 원칙 (LSP)에 대해 알아봅니다!

velog.io

 

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

리스코프 치환 원칙 - LSP (Liskov Substitution Principle) 리스코프 치환 원칙은 1988년 바바라 리스코프(Barbara Liskov)가 올바른 상속 관계의 특징을 정의하기 위해 발표한 것으로, 서브 타입은 언제나 기반

inpa.tistory.com


✏️ 마무리

리스코프 치환 원칙은 상속을 쓸 때 반드시 고려해야 하는 원칙이다.

“하위 클래스는 상위 클래스로 교체해도 프로그램의 정확성이 유지되어야 한다.”

  • 다형성을 이용하고 싶다면 `extends` 대신 인터페이스로 `implements` 하기
  • `is-a` 관계가 아닐 경우, 상속(inheritance)보다는 합성(composition)을 고려하기

다음 포스팅에서는 I - 인터페이스 분리 원칙 (Interface Segregation Principle) 을 다뤄보겠습니다.

👉 SOLID - I: 인터페이스 분리 원칙 (Interface Segregation Principle)

 

SOLID - I: 인터페이스 분리 원칙 (Interface Segregation Principle)

객체지향 프로그래밍(OOP)에서는 더 나은 코드 구조와 유지 보수를 위해 SOLID 원칙을 따른다.SOLID 원칙 중 네 번째인 I -인터페이스 분리 원칙 (ISP)를 알아보자.📌 인터페이스 분리 원칙(ISP)이란?Int

minsllogg.tistory.com

 

저작자표시 비영리 변경금지 (새창열림)

'Architecture & Design' 카테고리의 다른 글

SOLID - D: 의존 역전 원칙 (Dependency Inversion Principle)  (0) 2025.06.15
SOLID - I: 인터페이스 분리 원칙 (Interface Segregation Principle)  (1) 2025.06.15
SOLID - O: 개방/폐쇄 원칙 (Open/Closed Principle)  (1) 2025.06.15
SOLID 원칙 - S: 단일 책임 원칙(SRP)  (0) 2025.06.08
객체지향 프로그래밍(OOP)의 5가지 핵심 원칙 - SOLID  (1) 2025.06.08
'Architecture & Design' 카테고리의 다른 글
  • SOLID - D: 의존 역전 원칙 (Dependency Inversion Principle)
  • SOLID - I: 인터페이스 분리 원칙 (Interface Segregation Principle)
  • SOLID - O: 개방/폐쇄 원칙 (Open/Closed Principle)
  • SOLID 원칙 - S: 단일 책임 원칙(SRP)
밍구링구리
밍구링구리
밍구리의 실험실 이것저것 배우고 기록을 남깁니다
  • 밍구링구리
    Mingguri Labatory
    밍구링구리
  • 전체
    오늘
    어제
    • 분류 전체보기 (62) N
      • Language & Framework (3)
        • Java (1)
        • Vue.js (1)
        • Spring Boot (1)
      • Computer Science (0)
        • Operating System) (0)
        • Database (0)
        • Network (0)
        • Data Structure (0)
      • Architecture & Design (7)
      • Cloud & Infra (5)
      • Trouble Shooting (0)
        • 에러 해결 (0)
        • 삽질 기록 (0)
      • Coding Practice (28) N
        • Backjoon (17)
        • LeetCode (6)
        • Programmers (5) N
      • ETC (18)
        • 일상 (1)
        • 후기 (2)
        • 밍구의 실험실 (1)
        • 🤔Study . Question🔍 (14)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
    • 깃허브
    • 소개
  • 인기 글

  • 최근 글

  • 최근 댓글

  • 태그

    LeetCode
    스터디
    퀴즈
    골드4
    프로그래머스
    투포인터
    알고리즘
    객체지향
    코딩테스트
    문제
    BFS
    OOP
    git
    백준
    SOLID
  • hELLO· Designed By정상우.v4.10.3
밍구링구리
SOLID - L: 리스코프 치환 원칙 (Liskov Substitution Principle)
상단으로

티스토리툴바