글의 순서

  1. if-else의 문제점
  2. OCP (Open Close Principle)
  3. 전략 패턴 (Strategy Pattern)

OCP란?

Open Close Principle : 개방폐쇄의 원칙

시간이 지나도 유지 보수와 확장이 쉬운 시스템을 만들고자 로버트 마틴이 명명한 객체지향설계 5대 원칙 SOLID중 하나입니다.

OCP는 소프트웨어 구성 요소(컴포넌트, 클래스, 모듈, 함수)는 확장에 대해서는 개방(OPEN)되어야 하지만 변경에 대해서는 폐쇄(CLOSE)되어야 한다는 의미입니다.
즉, 기존의 코드를 변경하지 않으면서 기능을 추가할 수 있도록 설계가 되어야 한다는 의미입니다.

image.png

위 코드를 보시면 확장을 위해서 기존의 코드가 수정되었습니다. 이런 경우에 Close를 위반하게 됩니다. OCP에서 핵심은 Close입니다. 변경, 확장을 위해서 기존 코드가 수정되면 변경, 확장 Open도 힘들어집니다.

OCP 적용방법

OCP를 적용하는 방법은 두 가지가 있습니다.

  1. 상속 (is-a)
  2. 컴포지션 (has-a)

저는 주로 컴포지션을 이용합니다. 그 이유는 상속은 상위클래스가 바뀌면 하위클래스에 끼치는 영향이 매우 크다는 단점이 있기 때문입니다.

적용 방법은 3단계로 나눠집니다.

  1. 변경(확장)될 것과 변하지 않을 것을 엄격히 구분
  2. 이 두 모듈이 만나는 지점에 인터페이스를 정의
  3. 구현에 의존하기보다 정의한 인터페이스에 의존하도록 코드를 작성

1. 변경될 부분과 변하지 않을 부분을 엄격히 구분

image.png

변경될 부분과 변하지 않을 부분을 A, B, C로 구분을 지었습니다. 그리고 현재 shuffle을 해주는 B에서 변경이 생기고 있습니다. 이 부분을 인터페이스로 추출하겠습니다.


2. 모듈이 만나는 지점에 인터페이스 정의

image.png

Bshuffle() 메소드를 가진 ShuffleStratey 라는 인터페이스로 분리했습니다. 여기서 주의할 점은 인터페이스로 만들어야 합니다. 인터페이스는 변하는 것과 변하지 않는 모듈의 교차점으로 서로를 보호하는 방죽 역할을 합니다.

* 인터페이스가 아닌 구현 클래스에 직접 의존한다면 생기는 문제

image.png


3. 인터페이스에 의존하도록 코드를 작성

image.png

LottoNumbersAutoGeneratorShuffleStrategy 인터페이스에 의존하도록 해주고 생성자를 통해 구현 클래스의 인스턴스를 외부에서 주입해주는 방식으로 이용합니다. 이런 방식을 DI(Dependency Injection)라고 부릅니다.

기능을 바꿔도 내부의 코드는 변경이 일어나지 않는다.

다른 기능을 사용하고 싶을 때 클라이언트가 LottoNumbersAutoGenerator 생성자에 원하는 기능을 하는 인스턴스만 넣어주면 됩니다.

ex)

image.png

다른 기능을 사용하고 싶을 때 클라이언트가 LottoNumbersAutoGenerator 생성자에 원하는 기능을 하는 인스턴스만 넣어주면 됩니다.

이렇게 해줌으로써 기능이 추가, 수정이 되어도 기존의 코드(B)에는 영향이 가지 않습니다.

image.png

image.png

새로운 기능이 계속 추가되어도 기존의 코드는 변경되지 않습니다.

랜덤때문에 테스트 못하던 부분도 해결 가능

image.png

또 다른 장점은 구현체를 주입해서 테스트가 가능합니다. 이전에는 shuffle()이 랜덤이여서 테스트가 불가능했지만, 이렇게 하면 테스트가 가능합니다.

정리

OPEN : 확장에 열려있다.
CLOSE : 변경에 닫혀 있다.
기존의 코드를 변경하지 않으면서 기능을 추가할 수 있다.

Refrences