이번 포스트에서는 SOLID 원칙의 두 번째인 OCP 에 대해서 예시와 함께 다뤄보려고 한다.
OCP (Open Closed Principle)
OCP 는 Open Closed Principle 의 약어로, 개방 폐쇄의 원칙을 의미한다. OCP 의 개념은 다음과 같다.
Software entities should be open for extension, but closed for modification.
소프트웨어의 요소들은 확장에는 열려 있어야 하고, 변경에는 닫혀 있어야 한다.
좀 더 풀어서 설명하면, 기능이 확장될 때 기존 작성된 코드는 변경되지 않아야한다는 것이다.
어떻게 기존 코드의 변경 없이 기능을 확장할 수 있을까? 이 때 우리가 사용할 수 있는 핵심 개념이 객체 지향의 기본 개념인 추상화와 객체 지향의 3요소에 속했던 상속과 다형성이다.
Fig1 의 예시를 보자. ByteSource 인터페이스는 특정 소스에서 Byte 를 읽어오는 역할을 하는 추상화된 인터페이스이다. (Interface는 Class 와 다르게 반드시 구현해야하는 메서드나 프로퍼티를 포함한다. Class 의 하위 클래스를 만들 때는 상속받는다고 표현하지만, Interface 는 구현한다고 표현한다.)
- FileByteSource 는 파일에서 Byte를 읽어오는 역할을 하는 클래스이다.
- SocketByteSource 는 소켓에서 Byte를 읽어오는 역할을 하는 클래스이다.
ByteSource 를 구현하는 FileByteSource 와 SocketByteSource 는 ByteSource 를 상속받는다고 볼 수 있고, 다형성에 의해 ByteSource 객체의 참조값을 저장할 수 있는 변수에는 ByteSource 의 하위 타입 역시 담을 수 있다.
여기서 만약 메모리에서 Byte를 읽어오는 역할을 하는 클래스가 추가되어야 한다면, 우리는 단지 MemoryByteSource 라는 클래스가 ByteSource 인터페이스를 구현하도록 만들면된다.
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
FileByteSource, SocketByteSource, MemoryByteSource 등이 ByteSource 이기 때문에(is-a 관계) ByteSource 객체를 가리키는 변수에 저장할 때 ByteSource 타입으로 변환(캐스팅, 정확하게는 업캐스팅)된다. 이 과정은 크게 문제가 되지 않는다.
그러나 MemoryByteSource 객체가 저장된 ByteSource 타입 변수를 MemoryByteSource 로 변환하고 싶을 수 있다. MemoryByteSource 에서만 정의한 메서드를 사용해야 할 때이다. 이런 경우 DownCasting 이 가능하지만, 설계가 잘못되었을 가능성이 크다.
또한 설계한 추상화가 깨지면서, 확장이 될 때 수정해야할 가능성이 생기기 때문에, 더 이상 변경에 닫혀있지도 않게 된다.
만약 꼭 필요한 경우 타입 캐스팅 이후에 실행하는 메서드가 변화 대상이 아니도록 코드를 작성해야 한다.
SOLID 의 다른 원칙 바로가기
Reference
- 최범균, 2014, 객체지향과 디자인 패턴, 인투북스
- [Wikipedia] “SOLID” — https://en.wikipedia.org/wiki/SOLID
- [위키백과] “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)