오브젝트 - 09. 유연한 설계

강준혁·2022년 9월 18일
0

오브젝트

목록 보기
10/14
post-thumbnail

개방 - 폐쇄 원칙

"소프트웨어 개체(클래스, 모듈, 함수 등)는 확장에 대해 열려있어야 하고 수정에 대해서는 닫혀 있어야 한다."

  • 확장에 대해 열려있다 : 애플리케이션의 요구사항이 변경될 때 이 변경에 맞게 새로운 '동작'을 추가해서 애플리케이션 기능을 확장할 수 있다.
  • 수정에 대해 닫혀있다 : 기존의 '코드'를 수정하지 않고도 애플리케이션의 동작을 추가하거나 변경할 수 있다.

추상화가 핵심이다

개방 - 폐쇄 원칙의 핵심은 추상화에 의존하는 것이다.

추상화란 핵심적 부분만 남기고 불필요한 부분은 생략함으로써 복잡성을 극복하는 기법이다. 생략한 부분을 문맥에 적합한 내용으로 채워넣음으로써 각 문맥에 적합하게 기능을 구체화하고 확장할 수 있다.

생략되지 않고 남겨지는 부분은 다양한 상황에서의 공통점을 반영한 추상화의 결과물이다. 공통적 부분은 문맥이 바뀌더라도 변하지 않아야 한다.
따라서 추상화 부분은 수정에 대해 닫혀있다.
추상화를 통해 생략된 부분은 확장의 여지를 남긴다.
이것이 추상화가 개방 - 폐쇄 원칙을 가능하게 만드는 이유다.

단순히 어떤 개념을 추상화했다고 해서 수정에 닫혀있는 설계를 만들 수 있는 것은 아니다. 폐쇄를 가능하게 하는 것은 의존성의 방향이다. 수정에 대한 영향을 최소화하기 위해서는 모든 요소가 추상화에 의존해야 한다.

변경을 기반으로 추상화 해야 한다

수정에 대해 닫혀있고 확장에 대해 열려있는 설계는 공짜로 얻어지지 않는다. 변경에 의한 파급효과를 최대한 피하기 위해서는 변하는 것과 변하지 않는 것이 무엇인지를 이해하고 이를 추상화의 목적으로 삼아야만 한다.

추상화가 수정에 대해 닫혀있을 수 있는 이유는 변경되지 않을 부분을 신중하게 결정하고 올바른 추상화를 주의 깊게 선택했기 때문이라는 사실을 기억하라.

생성 사용 분리

결합도가 높아질수록 개방 - 폐쇄 원칙을 따르는 구조를 설계하기 어려워진다. 알아야 하는 지식이 많을수록 결합도도 높아진다. 특히 객체 생성에 대한 지식은 과도한 결합도를 초래하는 경향이 있다.

유연하고 재사용가능한 설계를 원한다면 객체와 관련된 두 가지 책임(생성에 대한 책임과 사용에 대한 책임)을 서로 다른 객체로 분리해야 한다.

Factory 추가하기

특정 객체의 생성에 대한 책임을 외부로 옮겼다면, 그 객체는 사용에 대한 책임만 가지지만 외부의 클라이언트는 생성에 대한 책임을 떠안게 된다.

만약 클라이언트가 생성에 대한 책임을 갖기를 원치 않는다면, 생성에 대한 책임만 전담하는 객체를 추가할 수 있는데 이를 FACTORY라고 한다.

순수한 가공물에게 책임 할당하기

시스템을 객체로 분해하는 데는 크게 두 가지 방식이 있다.

  • 표현적 분해 : 도메인에 존재하는 사물 또는 개념을 표현하는 객체를 이용해 시스템을 분해한다. 도메인 모델에 담겨있는 개념과 관계를 따르며 도메인과 소프트웨어 사이의 표현적 차이를 최소화하는 것을 목적으로 한다.
  • 행위적 분해 : 모든 책임을 도메인 객체에게 할당할 경우 낮은 응집도, 높은 결합도, 재사용성 저하와 같은 심각한 문제점에 봉착하게 될 가능성이 높아진다. 이 경우 도메인 개념을 표현한 객체가 아닌 설계자가 편의를 위해 임의로 만들어낸 가공의 객체(순수한 가공물, Pure Fabrication)에게 책임을 할당해서 문제를 해결해야 한다.

어떤 행동을 추가하려고 하는데 이 행동을 책임질 마땅한 도메인 개념이 존재하지 않는다면 순수한 가공물을 추가하고 이 객체에게 책임을 할당하라.

객체지향 애플리케이션은 도메인 개념뿐만 아니라 설계자들이 임의적으로 창조한 인공적 추상화들을 포함하고 있다.

설계자로서의 우리의 역할은 도메인 추상화를 기반으로 애플리케이션 로직을 설계하는 동시에 품질의 측면에서 균형을 맞추는데 필요한 객체들을 창조하는 것이다.

먼저 도메인의 본질적 개념을 표현하는 추상화를 통해 애플리케이션을 구축하기 시작하라.

도메인 개념이 만족스럽지 못하다면 주저말고 인공적인 객체를 창조하라.

우리의 목적은 사용자가 원하는 기능을 제공하기 위해서지 실세계를 모방하거나 시뮬레이션하기 위함이 아니다.

의존성 주입

생성과 사용을 분리하면 외부의 다른 객체가 사용하는 객체에게 생성된 인스턴스를 전달해야한다는 것을 의미한다.
이처럼 사용하는 객체가 아닌 외부의 독립적인 객체가 인스턴스를 생성한 후 이를 전달해서 의존성을 해결하는 방법을 의존성 주입이라고 한다.

숨겨진 의존성은 나쁘다

내부에서 new 를 사용하거나 Service Locator 패턴을 사용하여 의존성을 해결하는 경우, 해당 객체에 대한 외부로의 의존성이 퍼블릭 인터페이스에서 감춰진다.

이와 같이 의존성이 숨겨질 경우 다음과 같은 문제점이 발생한다.

  • 의존성 관련 문제가 컴파일타임이 아닌 런타임에 가서야 발견된다.
  • 단위테스트 작성이 어렵다.
  • 의존성을 이해하기 위해 코드의 내부 구현을 이해할 것을 강요한다.

의존성 역전의 원칙

의존성 역전의 원칙은 아래의 내용을 만족해야 한다.

  • 상위 수준의 모듈은 하위 수준의 모듈에 의존해서는 안된다. 둘 다 추상화에 의존해야 한다.
  • 추상화는 구체적인 사항에 의존해서는 안된다. 구체적인 사항은 추상화에 의존해야 한다.

유연성에 대한 조언

유연한 설계는 유연성이 필요할 때만 옳다

유연성은 항상 복잡성을 수반한다.
유연하지 않은 설계는 단순하고 명확하다.

설계가 유연할수록 클래스 구조와 객체 구조 사이의 거리는 멀어진다.
유연함은 단순성과 명확성의 희생 위에서 자라난다.

불필요한 유연성은 불필요한 복잡성을 낳는다.
유연성은 코드를 읽는 사람들이 복잡함을 수용할 수 있을 때만 가치가 있다.

협력과 책임이 중요하다

설계를 유연하게 만들기 위해서는 역할, 책임, 협력에 초점을 두어야 한다.
다양한 컨텍스트에서 협력을 재사용할 필요가 없다면 설계를 유연하게 만들 당위성도 사라진다.

profile
백엔드 개발자

0개의 댓글