소스 코드를 기록하는 남자

SOLID - OCP ( 개방-폐쇄 원칙 ) 에 대하여

OOP

객체 지향에 대해서 본격적인 공부를 시작하면서 보게 된 포스팅이 매우 인상 깊어 글을 작성하게 되었다.

아래는 원본글이니, 객체 지향을 공부하기 시작한 사람이라면 꼭 볼만한 포스팅이라 생각한다.

 

[객체지향 SW 설계의 원칙] ① 개방-폐쇄 원칙

 

[객체지향 SW 설계의 원칙] ① 개방-폐쇄 원칙

소프트웨어 설계의 묘미는 개발자가 작업하는 시스템의 창조자 역할을 할 수 있다는 것이다. 실세계의 경우 좋은 세상을 만들기 위해 적절한 질서, 정책, 의식 등이 전제돼야 하...

zdnet.co.kr

개방-폐쇄 원칙

버틀란트 메이어는 개방-폐쇄 원칙을 다음과 같이 정의한다.

 

소프트웨어 구성 요소 ( 컴포넌트, 클래스, 모듈, 함수 ) 는 확장에 대해서는 개방되어야 하며, 변경에 대해서는 폐쇄되어야 한다. 이는 다시 말해 변경을 위한 비용은 가능한 줄이고 확장을 위한 비용은 가능한 극대화해야 한다는 의미다.

 

이를 달성하기 위해서는 변경되는 부분, 변경되지 않는 부분을 엄격하게 구분해야 한다. 구분을 통해서 변하는 것은 가능한 변하게 쉽게하고, 변하지 않는 것은 변하는 것에 영향을 받지 않도록 하는 것이다. 그리고 인터페이스를 변하는 것과 변하지 않는 것의 중간 지점에서 서로를 보호하는 중재자 역할을 해야된다.

 

따라서, 이를 통해서 얻을 수 있는 것은 객체 간의 관계를 단순화해 복잡도를 줄이고, 확장이나 변경에 의해 발생하는 충격을 줄일수 있다.

 

혹시 복잡한가?

 

그럼 좀 더 쉽게 로버트 C.마틴의 말을 인용해보겠다. "소프트웨어 엔티티는 확장에 대해서는 열려 있어야하고 , 변경에 대해서는 닫혀있어야 된다." 라는 말을 쉽게 풀어보면 "나는 확장될 수 있어야하며, 다른 사람이 변하더라도 나에게 영향을 끼치면 안된다". 좀 더 이해가 됐으면 좋겠다.

 

자, 위 포스팅에서는 중재자의 역할, OCP의 인터페이스 기능으로 하는 예로 24핀 표준 규격의 충전기를 든다. 공통된 규격의 충전기는 사용자가 충전기를 사용함에 있어서 신뢰할 수 있고, 충전기 제공자는 목적에 맞게 확장, 특화하여 차별화나 상품성을 높일 수 있다. 물론 지금은 USB-C type이 표준이라 볼 수 있겠다.

 

하나의 예를 더 들어보자.

 

스프링 입문을 위한 자바 객체 지향의 원리와 이해에 들어있는 예제를 보겠다..

 

위 [그림 1]을 보게 되면 운전자는 기존에 마티즈를 타다가 새로운 자동차 소나타를 구매했다. 차량 종류를 변경하자 기본에 마티즈에서 수동으로 하던 일을 자동으로 변경되었다. 단순히 스틱 차량에서 오토 차량으로 변경되었다고 해서 기존의 운전자의 역할이 변화되어야 되는가? 현실 세계에서 봤을때는 어느 정도 Yes 라는 대답은 하겠지만, 객체 지향에서는 아니다. OCP에 위반되기 때문이다. 그럼 어떻게 해야하는가?

 

 

다소 위 예제가 억지스럽다고 느낄 수 있지만, 그려려니 해주셨으면 한다.

[그림 2]와 같이 기존의 공통된 특성을 추출하여 클래스나, 인터페이스를 생성하면 운전자 클래스는 다양한 자동차가 생긴다 하더라도 운전자 클래스에는 영향을 끼치게 않게 된다. 또한 자동차 입장에서는 확장에는 개방되어 있다.

 

개방 폐쇄 원칙에 대해서 이 글을 보며 조금이라도 이해했다면? 대표적인 개방 폐쇄 원칙을 지킨 가장 큰 예제는 JDBC이다.

JDBC를 사용하는 클라이언트는 데이터베이스가 오라클, MySQL, MariaDB 어떤 데이터베이스를 사용하더라도 Connection을 설정하는 부분을 제외하고는 모두 동일하게 사용 가능하다.

 

자신의 코드는 수정할 필요가 없이 변화에 대해서는 닫혀있으며, 다양한 데이터베이스 연결에 대해서는 확장에 열려있다고

볼 수 있다. 이러한 개방 폐쇄 원칙을 잘 지킴으로써 완충 역할을 하고 있는 것이다.

OCP 주의점

여기서 3가지 주의점에 대해서 언급한다. 아직 주니어 개발자라 격하게 공감하지 못하지만 명언처럼 마음에 담아두게 되었다.

  1. 공통 모듈 설계

    • 공통된 루틴이나 변수를 리팩토링하여 분리를 하게 되었을 때, 공통 모듈이 작을 경우 공통 모듈 재사용을 얻기 위해 너무 잦은 모듈을 접근해야 하고 모듈 구성도를 지저분하게 할 경향이 높다. 설계자의 좋은 자질 중 하나는 OCP에서 확장되는 것과 변경되지 않는 모듈을 분리하는 과정에서 크기 조절을 잘 할 수 있는 결단력이라는 것이다.

  2. 확장을 보장하는 Open 모듈에서 인터페이스의 변경인가, 어댑터 사용인가

    • 확장을 보장하는 open 모듈 영역에서 예측하지 못한 확장 타입을 만났을 때 인터페이스 변경하려는 안과 어댑터를 사용하려는 안 사이에서 갈등하게 된다. 위의 두 예에서처럼 변경의 충격이 적은 후자를 택하는 경우가 대부분이다. 한 번 정해진 인터페이스는 시간이 갈수록 사용하는 모듈이 많아지기 때문에 바꾸는데 엄청난 출혈을 각오해야 한다. 그 대표적인 예가 자바의 deprecated API라 한다.

      인터페이스는 가능하면 변경하면 안 되며, 이를 달성하기 위해서는 여러 경우의 수에 대한 고려와 예측이 필요하다. 과도한 예측은 불필요한 작업을 만들기에, 설계자의 적절한 예측 능력이 필요하다.

  3. 커맨드의 역할

    • 요청자와 처리자 사이의 계약을 커맨드라 지칭하고 있다. 여기서 처리자는 execute()란 인터페이스만 알면 어떠너 처리도 수행할 수 있다. 따라서 서로 의미적 관계가 없는 Command들도 execute()란 메소드로 무엇이든 확장할 수 있다. OCP 구조에서 서버가 확장할 수 있는 운신의 폭이 넓어진 반면 클라이언트는 서버가 어떤 처리를 하는지 무지해진다.

      즉, 인터페이스 설계에서 적당한 추상화 레벨을 선택하는 것이 중요하다. 추상화라는 개념에 '구체적이지 않은' 정도의 의미로 약간 느슨한 개념을 갖고 있다.

      그래디 부치가 말하는 추상화는 '추상화란 다른 모든 종류의 객체로부터 식별될 수 있는 객체의 본질적인 특징' 이라고 한다. 따라서 이 '행위'에 대한 본질적인 정의를 통해 인터페이스를 식별해야 한다.

      포스팅에서 유일하게 이해되지 않는 부분은 '행위' 라는 부분이다. 혹여라도 지나가시던 객체 지향 고수분이 있으시다면 댓글을 큰 해소점이 될 것 같다.