CS 정리➊

Dear·2025년 10월 31일

TIL

목록 보기
73/74

💙 정적(static)과 동적(dynamic)

구분정적(Static)동적(Dynamic)
시점컴파일 시 결정실행 시 결정
변화 여부고정되어 있음실행 중에 바뀔 수 있음
안정성컴파일러가 미리 오류를 잡음실행하면서 유연하게 처리
대표 언어Java, C, C++ (정적 타이핑)Python, JavaScript (동적 타이핑)

즉, "정적"은 프로그램 실행 전에 다 정해놓고 시작하는 방식이며 "동적"은 프로그램 실행 중에 바뀌거나 결정되는 방식이다.

💙 다른 측면의 정적과 동적

구분정적동적
바인딩(Binding)어떤 함수/메서드를 호출할지 컴파일 시점에 결정실행 시점에 결정 (예: 오버라이딩)
메모리 할당컴파일 시 고정된 크기실행 중에 필요할 때 생성/할당
다형성오버로딩(정적 다형성)오버라이딩(동적 다형성)

  • 정적 = 안정성, 빠름
  • 동적 = 유연성, 편리함

💙 SOLID 원칙

객체지향 프로그래밍을 설계할 때 지켜야할 원칙

S : 단일 책임 원칙

O : 개방-폐쇄 원칙

L : 리스코프 치환 원칙

I : 인터페이스 분리 원칙

D : 의존 역전 원칙

💚 리스코프 치환 원칙

부모 타입의 객체를 자식 타입의 객체로 교체해도, 프로그램의 동작이 깨지지 않아야 한다
Parent p = new Child(); 처럼 부모 자리에 자식을 넣어도 Parent로서의 기능은 그대로 잘 작동해야 한다는 뜻이다.

자식은 부모보다 기능이 많을 수도 있는데, 어떻게 바꿔 끼워도 문제가 안 되는 이유

"부모 객체에 자식을 넣는다"의 대표적인 자바 코드가 있다.
List<String> list = new ArrayList<>();
ArrayList는 List의 하위 타입이다.
그런데 List로 선언해도 ArrayList의 기능(추가, 삭제 등)은 정상 작동고 있다.

이게 바로 리스코프 치환 원칙이 잘 지켜진 경우로 자식 클래스가 부모의 “약속된 행위(계약)”를 그대로 지키고 있다면, 내부에 어떤 속성이나 기능이 더 있든 상관없다.

즉, 부모가 약속한 기능(메소드)은 그대로 작동해야하며 부모가 보장한 입력/출력 규칙도 깨면 안 된다.

올바른 치환 (LSP 준수) 코드

class Bird {
    void fly() { System.out.println("날 수 있다"); }
}

class Sparrow extends Bird {
    // 부모의 규약을 그대로 지킴
}

Bird b = new Sparrow();
b.fly(); // "날 수 있다" → 정상

Sparrow는 Bird가 약속한 “fly 가능”을 그대로 지키고 있어 LSP를 만족하고 있다.

잘못된 치환 (LSP 위반)

class Bird {
    void fly() { System.out.println("날 수 있다"); }
}

class Penguin extends Bird {
    @Override
    void fly() { 
        throw new UnsupportedOperationException("펭귄은 날 수 없음"); 
    }
}

Bird b = new Penguin();
b.fly(); // 예외 발생

문법상으로는 “자식이 부모를 대체”했지만, 부모가 약속한 “fly() 가능”이라는 계약을 깨버렸다.
즉, LSP 위반이다.

무조건 교체 가능이 아니라 “부모가 약속한 행위를 그대로 지킬 수 있다면 교체 가능”하다.

LSP의 진짜 의도는 “논리적 일관성 유지”이다. 서브타입은 진짜로 상위타입의 의미를 확장(extends)하는 관계여야 한다. 부모가 무엇을 할 수 있는가를 약속했으면 자식도 그 약속을 위반하지 않아야 한다.

profile
친애하는 개발자

0개의 댓글