[OOP] SOLID 원칙2— OCP(Open Closed Principle)

dEpayse
6 min readNov 1, 2022

이번 포스트에서는 SOLID 원칙의 두 번째인 OCP 에 대해서 예시와 함께 다뤄보려고 한다.

OCP (Open Closed Principle)

OCP 는 Open Closed Principle 의 약어로, 개방 폐쇄의 원칙을 의미한다. OCP 의 개념은 다음과 같다.

Software entities should be open for extension, but closed for modification.

소프트웨어의 요소들은 확장에는 열려 있어야 하고, 변경에는 닫혀 있어야 한다.

좀 더 풀어서 설명하면, 기능이 확장될 때 기존 작성된 코드는 변경되지 않아야한다는 것이다.

어떻게 기존 코드의 변경 없이 기능을 확장할 수 있을까? 이 때 우리가 사용할 수 있는 핵심 개념이 객체 지향의 기본 개념인 추상화와 객체 지향의 3요소에 속했던 상속과 다형성이다.

Fig1. OCP 고려한 설계 예시

Fig1 의 예시를 보자. ByteSource 인터페이스는 특정 소스에서 Byte 를 읽어오는 역할을 하는 추상화된 인터페이스이다. (Interface는 Class 와 다르게 반드시 구현해야하는 메서드나 프로퍼티를 포함한다. Class 의 하위 클래스를 만들 때는 상속받는다고 표현하지만, Interface 는 구현한다고 표현한다.)

  • FileByteSource 는 파일에서 Byte를 읽어오는 역할을 하는 클래스이다.
  • SocketByteSource 는 소켓에서 Byte를 읽어오는 역할을 하는 클래스이다.

ByteSource 를 구현하는 FileByteSource 와 SocketByteSource 는 ByteSource 를 상속받는다고 볼 수 있고, 다형성에 의해 ByteSource 객체의 참조값을 저장할 수 있는 변수에는 ByteSource 의 하위 타입 역시 담을 수 있다.

Ex1. OCP 에서 사용되는 객체 지향 개념들을 보여주는 예시

여기서 만약 메모리에서 Byte를 읽어오는 역할을 하는 클래스가 추가되어야 한다면, 우리는 단지 MemoryByteSource 라는 클래스가 ByteSource 인터페이스를 구현하도록 만들면된다.

Fig2. OCP 고려한 설계에서 기능 추가

MemoryByteSource 클래스 내에서는 ByteSource 의 메서드를 반드시 재정의해야하고, FileByteSource 혹은 SocketByteSource 와는 다르게 메모리에서 ByteSource 를 읽어오도록 만들 수 있다. 또한 ByteSource 를 사용하는 곳에서는 ByteSource 로 추상화되어 있기 때문에 이를 하위 타입으로 강제 캐스팅하지 않았던 이상 기존 코드는 변경하지 않아도 동작시킬 수 있다.

OCP 를 지키는 핵심 개념

예시를 통해 봤듯이, 객체 지향의 여러 가지 개념들을 사용해서 OCP 원칙을 지킬 수 있었다. 어떻게 OCP 가 지켜지는지 좀 더 자세히 살펴보려고한다.

  • 상속다형성이 확장에 열려있도록 만든다. (Open for extension)

FileByteSource, SocketByteSource, MemoryByteSource 등 ByteSource 라는 하나의 인터페이스를 구현하게 함으로써 확장에 열려있도록한다. 또한, ByteSource 인터페이스를 구현하는 클래스가 ByteSource 타입으로 사용될 수 있게 하는 Polymorphic Subtypes 와 Method Overriding 이라는 다형성이 확장에 열려있도록 만든다. (런타임에 해당 하위 클래스 타입으로 바인딩되기 때문에 가능하다.)

  • 추상화는 변경에 닫혀있도록 만든다. (Closed for modification)

한편, FlowController 클래스 입장에서는 collect 라는 함수를 사용할 때 받는 인자가 FileByteSource 인지, SocketByteSource 인지 알 필요 없이 추상화된 ByteSource 라는 인자만을 사용하여 변경에는 닫혀있도록 만든다.

OCP 를 지키기 위해 지양해야할 점 — DownCasting

Ex2. UpCasting & DownCasting 예시

FileByteSource, SocketByteSource, MemoryByteSource 등이 ByteSource 이기 때문에(is-a 관계) ByteSource 객체를 가리키는 변수에 저장할 때 ByteSource 타입으로 변환(캐스팅, 정확하게는 업캐스팅)된다. 이 과정은 크게 문제가 되지 않는다.

그러나 MemoryByteSource 객체가 저장된 ByteSource 타입 변수를 MemoryByteSource 로 변환하고 싶을 수 있다. MemoryByteSource 에서만 정의한 메서드를 사용해야 할 때이다. 이런 경우 DownCasting 이 가능하지만, 설계가 잘못되었을 가능성이 크다.

또한 설계한 추상화가 깨지면서, 확장이 될 때 수정해야할 가능성이 생기기 때문에, 더 이상 변경에 닫혀있지도 않게 된다.

만약 꼭 필요한 경우 타입 캐스팅 이후에 실행하는 메서드가 변화 대상이 아니도록 코드를 작성해야 한다.

SOLID 의 다른 원칙 바로가기

Reference

  1. 최범균, 2014, 객체지향과 디자인 패턴, 인투북스
  2. [Wikipedia] “SOLID” — https://en.wikipedia.org/wiki/SOLID
  3. [위키백과] “SOLID (객체 지향 설계)” — https://ko.wikipedia.org/wiki/SOLID_(%EA%B0%9D%EC%B2%B4_%EC%A7%80%ED%96%A5_%EC%84%A4%EA%B3%84)

--

--

dEpayse

나뿐만 아니라 다른 사람들도 이해할 수 있도록 작성하는, 친절한 블로그를 목표로.