확장에는 열려있어야 하고, 변경에는 닫혀 있어야 한다는 원칙
- 확장에 열려있다 : 요구사항이 변경될 때 새로운 동작을 추가하여 기능을 확장
- 변경에 닫혀있다 : 기존의 코드를 수정하지 않고 동작을 추가하거나 변경
즉, 기존의 코드를 변경하지 않고 기능을 수정하거나 추가할 수 있도록 설계해야 한다
이를 지키지 않으면 어떤 모듈의 기능을 하나 수정할 때, 그 모듈을 이용하는 다른 모듈들 역시 줄줄이 고쳐야 한다면 유지보수가 복잡해진다. 따라서 개방 폐쇄 원칙을 잘 적용하여 기존 코드를 변경하지 않아도 기능을 새롭게 만들거나 변경할 수 있도록 해야 한다.
그렇지 않으면 객체지향 프로그래밍의 가장 큰 장점인 유연성, 재사용성, 유지보수성 등을 모두 잃어버리는 셈이고, OOP
를 사용하는 의미가 사라지게 된다.
OCP
는 추상화(프로토콜)와 상속(다형성) 등을 통해 구현해낼 수 있다. 자주 변화하는 부분을 추상화함으로써 기존 코드를 수정하지 않고도 기능을 확장할 수 있도록 함으로써 유연함을 높이는 것이 핵심이다.
여기서 본질적으로 이야기하는 것은 추상화이며, 결국은 런타임 의존성과 컴파일타임 의존성에 대한 이야기이다.(이부분은 다음 글에 작성해보겠습니다)
그럼 예를 든다면 대표적으로 프로그래밍시 사용하는 라이브러리를 생각해보겠습니다. 라이브러리를 사용하는 객체의 코드가 변경된다고 해서 라이브러리의 코드까지 변경하지는 않습니다.
결론적으론 객체가 알아야 하는 정보가 많으면 결합도가 높아지고, 결합도가 높아질수록 개방 패쇄의 원칙을 따르는 구조를 설계하기가 어려워진다. 추상화를 통해 변하는 것들을 숨기고 변하지 않는 것들에 의존하게 하면 우리는 기존의 코드 및 클래스들을 수정하지 않은채 확장가능하다는 것이다.
OCP를 위배한 예시 코드
Non_OCP
클래스 안에서 각 연산 클래스의 인스턴스를 생성해 클래스간의 결합도가 높게 연결 되어있다. 그렇다면 두번째 사진과 같이 연결된 클래스들 중에서 하나의 클래스(저수준 모듈)가 수정되거나 없어진다면, 사진과 같이 고수준 모듈인Non_OCP
클래스에 에러가 발생하게 된다.OCP를 준수한 예시 코드
OCP클래스
에서는 calculateProtocol
을 사용하기 위해 인스턴스로 생성했다. 그렇게 내가 계산을 하려고 한다면, 생성한 OCP클래스
의 인스턴스에 calculateProtocol
을 채택한 클래스들 중에서 해당 연산 클래스를 사용하면 된다. OCP클래스
는 calculateProtocol
에 의존을 하기에 두번째 코드처럼 연산클래스(저수준 모듈)가 사라지거나 수정이 되어도 OCP클래스
(고수준 모듈)에는 영향이 가지 않는다.하위 타입 객체는 상위 타입 객체로 대체할 수 이었아하며 이는 하위 타입 객체가 상위 객체 타입을 완전치 준수해야한다.
즉, 상위 타입 객체를 하위 타입 객체로 치환해도 정상적으로 동작해야 한다
리스코프 치환 원칙(LSP)은 상위 타입과 하위 타입 간의 계약을 통해 상속 관계의 일관성과 신뢰성을 보장하는 중요한 원칙이다.
LSP의 중요성
LSP를 위배한 예시 코드
Rectangle클래스
를 상위 타입으로 설정하고 상위 타입인 Rectangle클래스
를 상속한 Square클래스
를 볼 수 있다. Rectangle클래스
를 인스턴스로 생성하면 정의 한대로 길이가 다른 높이와 너비를 받아 계산을 한다. 하지만 정사각형은 하나의 길이를 필요로하지만 상위 타입인 Rectangle클래스
를 상속받았기에 높이와 너비를 입력받는다. 그리고 LSP원칙에 의거하면 상위 타입으로 대체되더라도 같은 동작을 해야하지만 하위 타입인 Square클래스
는 다른 동작으로 계산을 한다. 즉 다른 의미를 가지는 메서드 오버라이딩과 상위 클래스로 대체 시 문제가 발생하게 된다.LSP를 준수한 예시 코드
Rectangle클래스
와 Square클래스
를 따로 생성하고 공통된 추상 클래스 즉 프로토콜을 생성해 채택하도록 했다. Shape
는 상위 타입이 되고 나머지 Rectangle클래스
와 Square클래스
는 하위 타입이 된 것이다.Square클래스
는 더 이상 Rectangle클래스
를 상속받지 않으며, 각 클래스는 자신의 책임을 명확히 하고, 상위 타입의 계약을 준수하게 된다.결국은, 리스코프 치환 원칙을 지키지 않으면 개방 폐쇄 원칙을 위반하게 되는 것이다. 기능 확장을 위해 기존의 코드를 여러 번 수정해야 할 것이다. 따라서 상속 관계를 잘 정의하여 LSP 원칙이 위배되지 않도록 설계해야 한다.