[cs]객체지향 5원칙(SOLID)_OCP,LSP

Jeff·2024년 11월 1일
1

객체지향 5원칙 (SOLID)

  • 단일 책임 원칙 (Single Responsibility principle)
  • 개방 폐쇄 원칙 (Open Close Principle)
  • 리스코프 치환 원칙 (Liscov Substitution Principle)
  • 인터페이스 분리 원칙 (Interface Sergregation Principle)
  • 의존성 역전 원칙 (Dependency Inversion Principle)

# OCP (Open-Closed) 개방-폐쇄 원칙

확장에는 열려있어야 하고, 변경에는 닫혀 있어야 한다는 원칙

  • 확장에 열려있다 : 요구사항이 변경될 때 새로운 동작을 추가하여 기능을 확장
  • 변경에 닫혀있다 : 기존의 코드를 수정하지 않고 동작을 추가하거나 변경
    즉, 기존의 코드를 변경하지 않고 기능을 수정하거나 추가할 수 있도록 설계해야 한다

이를 지키지 않으면 어떤 모듈의 기능을 하나 수정할 때, 그 모듈을 이용하는 다른 모듈들 역시 줄줄이 고쳐야 한다면 유지보수가 복잡해진다. 따라서 개방 폐쇄 원칙을 잘 적용하여 기존 코드를 변경하지 않아도 기능을 새롭게 만들거나 변경할 수 있도록 해야 한다.

그렇지 않으면 객체지향 프로그래밍의 가장 큰 장점인 유연성, 재사용성, 유지보수성 등을 모두 잃어버리는 셈이고, OOP를 사용하는 의미가 사라지게 된다.

OCP는 추상화(프로토콜)와 상속(다형성) 등을 통해 구현해낼 수 있다. 자주 변화하는 부분을 추상화함으로써 기존 코드를 수정하지 않고도 기능을 확장할 수 있도록 함으로써 유연함을 높이는 것이 핵심이다.
여기서 본질적으로 이야기하는 것은 추상화이며, 결국은 런타임 의존성과 컴파일타임 의존성에 대한 이야기이다.(이부분은 다음 글에 작성해보겠습니다)

그럼 예를 든다면 대표적으로 프로그래밍시 사용하는 라이브러리를 생각해보겠습니다. 라이브러리를 사용하는 객체의 코드가 변경된다고 해서 라이브러리의 코드까지 변경하지는 않습니다.

결론적으론 객체가 알아야 하는 정보가 많으면 결합도가 높아지고, 결합도가 높아질수록 개방 패쇄의 원칙을 따르는 구조를 설계하기가 어려워진다. 추상화를 통해 변하는 것들을 숨기고 변하지 않는 것들에 의존하게 하면 우리는 기존의 코드 및 클래스들을 수정하지 않은채 확장가능하다는 것이다.

# 예시 코드

  • OCP를 위배한 예시 코드

    • 위의 첫번째 코드를 보시면 Non_OCP클래스 안에서 각 연산 클래스의 인스턴스를 생성해 클래스간의 결합도가 높게 연결 되어있다. 그렇다면 두번째 사진과 같이 연결된 클래스들 중에서 하나의 클래스(저수준 모듈)가 수정되거나 없어진다면, 사진과 같이 고수준 모듈인Non_OCP클래스에 에러가 발생하게 된다.
    • 위의 코드처럼 하나의 클래스(저수준 모듈) 즉 기능(곱셈)이 추가가 된다면, 의존하는 클래스(고수준 모듈) 또한 수정이 이루어져야한다.
  • OCP를 준수한 예시 코드

    • 위의 첫번째 코드를 보시면 protocol이라는 키워드는 공통으로 수행해하는 함수 등을 추상화한 클래스와 같다. 그리고 각 연산 클래스에 protocol을 채택해서 protocol에 정의한 함수를 각자의 맞게 적용해 사용한다.
    • 그리고 OCP클래스에서는 calculateProtocol을 사용하기 위해 인스턴스로 생성했다. 그렇게 내가 계산을 하려고 한다면, 생성한 OCP클래스의 인스턴스에 calculateProtocol을 채택한 클래스들 중에서 해당 연산 클래스를 사용하면 된다.
    • 즉, OCP클래스calculateProtocol에 의존을 하기에 두번째 코드처럼 연산클래스(저수준 모듈)가 사라지거나 수정이 되어도 OCP클래스(고수준 모듈)에는 영향이 가지 않는다.
    • 또한 위의 사진처럼 하나의 클래스(저수준 모듈) 즉 기능(곱셈)이 추가가 되어도 의존하는 클래스(고수준 모듈)에는 아무런 수정이 없어도 사용이 가능하다.

LSP (Liskov Substitution) 리스코프 치환 원칙

하위 타입 객체는 상위 타입 객체로 대체할 수 이었아하며 이는 하위 타입 객체가 상위 객체 타입을 완전치 준수해야한다.
즉, 상위 타입 객체를 하위 타입 객체로 치환해도 정상적으로 동작해야 한다

리스코프 치환 원칙(LSP)은 상위 타입과 하위 타입 간의 계약을 통해 상속 관계의 일관성과 신뢰성을 보장하는 중요한 원칙이다.

LSP의 중요성

  • 코드의 신뢰성을 보장한다 : 상위 타입을 사용하는 코드가 하위타입에도 동일하게 작동할 수 있기에 코드의 신뢰성과 일관성을 유지 할 수 있다.
  • 유연성과 확장성을 제공한다 : 하위 타입이 상위 타입을 완전히 대체할 수 있으므로, 코드의 유연성관 확장성을 높일 수 있어 새로운 하위 타입을 추가하더라도 기존의 코드를 수정 할 필요가 없다.
  • 테스트 용이성 : 상위 타입의 테스트 케이스를 하위 타입에서도 동일하게 적용할 수 있기 때문이다.

# 예시 코드

  • LSP를 위배한 예시 코드

    • 직사각형과 정사각형에 대한 예시이다. 사실 수학적인 측면에서 정사각형도 직사각형이라고 할 수 있지만 프로그래밍에서는 오류가 발생한다.
    • 위의 코드를 보시면 Rectangle클래스를 상위 타입으로 설정하고 상위 타입인 Rectangle클래스를 상속한 Square클래스를 볼 수 있다.
    • 그 다음으로 Rectangle클래스를 인스턴스로 생성하면 정의 한대로 길이가 다른 높이와 너비를 받아 계산을 한다. 하지만 정사각형은 하나의 길이를 필요로하지만 상위 타입인 Rectangle클래스를 상속받았기에 높이와 너비를 입력받는다. 그리고 LSP원칙에 의거하면 상위 타입으로 대체되더라도 같은 동작을 해야하지만 하위 타입인 Square클래스는 다른 동작으로 계산을 한다. 즉 다른 의미를 가지는 메서드 오버라이딩과 상위 클래스로 대체 시 문제가 발생하게 된다.
  • LSP를 준수한 예시 코드

    • 위 코드는 LSP원칙을 준수한 코드이다. 우선은 Rectangle클래스Square클래스를 따로 생성하고 공통된 추상 클래스 즉 프로토콜을 생성해 채택하도록 했다.
    • 그렇다면 여기서 프로토콜로 정의한 Shape는 상위 타입이 되고 나머지 Rectangle클래스Square클래스는 하위 타입이 된 것이다.
    • 이렇게 넓이를 구하는 공통된 메서드를 상위 타입에서 가져와 하위 클래스에서 각자에 맞게 정의해 사용하기에 클래스가 각각 독립적으로 구현된다. Square클래스는 더 이상 Rectangle클래스를 상속받지 않으며, 각 클래스는 자신의 책임을 명확히 하고, 상위 타입의 계약을 준수하게 된다.

결국은, 리스코프 치환 원칙을 지키지 않으면 개방 폐쇄 원칙을 위반하게 되는 것이다. 기능 확장을 위해 기존의 코드를 여러 번 수정해야 할 것이다. 따라서 상속 관계를 잘 정의하여 LSP 원칙이 위배되지 않도록 설계해야 한다.

profile
기본에 충실한 개발자가 목표!

0개의 댓글