Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

오브젝트, CHAPTER 09 유연한 설계, 2024-09-22 #467

Closed
Tracked by #436
jongfeel opened this issue Sep 22, 2024 · 0 comments
Closed
Tracked by #436

오브젝트, CHAPTER 09 유연한 설계, 2024-09-22 #467

jongfeel opened this issue Sep 22, 2024 · 0 comments
Assignees
Labels
GDG GDG Songdo and Incheon activity

Comments

@jongfeel
Copy link
Owner

CHAPTER 09 유연한 설계

01 개방-폐쇄 원칙

Open-Closed Principle, OCP

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

확장과 수정은 애플리케이션의 동작과 코드의 관점을 반영한다.

  • 변경에 맞게 새로운 동작을 추가해서 애플리케이션의 기능을 확장할 수 있다
  • 기존의 코드를 수정하지 않고도 애플리케이션의 동작을 추가하거나 변경할 수 있다

컴파일 타임 의존성을 고정시키고 런타임 의존성을 변경하라

사실은 런타임 의존성과 컴파일타임 의존성에 관한 이야기이다.
런타임 의존성은 실행시에 협력에 참여하는 객체들 사이의 관계이고
컴파일타임 의존성은 코드에서 드러나는 클래스들 사이의 관계이다.

영화 예매 시스템에서 할인 정책 설계는 개방-폐쇄 원칙을 따르고 있다.

추상화가 핵심이다

개방-폐쇄 원칙의 핵심은 추상화에 의존하는 것이다.
추상화 부분은 수정에 대해 닫혀 있다.
추상화를 통해 생략된 부분은 확장의 여지를 남긴다.

추상화를 해도 모든 수정에 대해 설계가 폐쇄되는 것은 아니다.
변경에 대한 파급효과를 최대한 피하기 위해서는 변하는 것과 변하지 않는 것이 무엇인지를 이해하고 이를 추상화의 목적으로 삼아야 한다.
추상화가 수정에 닫혀 있을 수 있는 이유는 변경되지 않을 부분을 신중하게 결정하고 올바른 추상화를 주의 깊게 선택했기 때문이라는 사실을 기억해야 한다.

02 생성 사용 분리

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

객체 생성은 피할 수는 없고 어딘가에서 생성은 해야 한다.
생성을 부적절한 곳에서 하는 것이 문제이다.

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

소프트웨어 시스템은 시작 단계와 실행 단계를 분리해야 한다 [Martin08]

FACTORY 추가하기

생성과 사용을 분리하기 위해 객체 생성에 특화된 객체를 FACTORY라고 부른다 [Evans03]

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

FACTORY는 도메인 모델에 속하지 않으며 순수한 기술적인 결정으로 추가한 것이다.
결합도를 낮추고 재사용성을 높이기 위해 도메인 개념에 할당돼 있던 객체 생성 책임을
도메인 개념과 아무 상관이 없는 가공의 객체로 이동시킨 것이다.

크라이그 라만은 시스템을 객체로 분해하는 데 두 방식이 있다고 했다.
표현적 분해(representational decomposition)과
행위적 분해(behavioral decomposition)이다. [Larman04]

표현적 분해는 도메인에 존재하는 사물 또는 개념을 표현하는 객체들을 이용해 시스템을 분해하는 것이다.
도메인 모델에 담겨 있는 개념과 관계를 따르며 도메인과 소프트웨어 사이의 표현적 차이를 최소화한다.

실제 동작하는 애플리케이션은 데이터베이스 접근을 하는 객체 처럼 도메인 개념을 넘는 기계적인 개념을 필요로 한다.
크레이그 라만은 책임을 할당하기 위해 창조되는 도메인과 무관한 인공적인 객체를
PURE FABRICATION(순수한 가공물)이라고 부른다 [Larman04]

어떤 행동을 책임질 마땅한 도메인 개념이 없으면 PURE FABRICATION을 추가하고 책임을 할당한다.
이는 표현적 분해보다 행위적 분해에 의해 생성되는 것이 일반적이다.

그래서 객체지향이 실세계의 모방이라는 말은 맞는 말이 아니다.
도메인 개념 외에도 설계자들이 창조한 인공적인 추상화가 포함되어 있기 때문이다.

레베가 워프스브록은
"애플리케이션 모델은 사용자에게 반응하고, 실행을 제어하며,
외부 리소스에 연결하는 컴퓨터 객체를 이용해 도메인 모델을 보충한다.[Wirfs-Brock03]"
라고 했다.
도메인 개념을 표현하는 객체와 창조된 가공의 객체들이 모여 협력하는 애플리케이션을 설계하는 것이 목표여야 한다.

03 의존성 주입

사용하는 객체가 아닌 외부의 독립적인 객체가 인스턴스를 생성한 후 이를 전달해서 의존성을 해결하는 방법을
의존성 주입(Dependency Injection)[Fowler04]라고 부른다.

  • 생성자 주입
  • setter 주입
  • 메서드 주입

의 방법이 있다.

숨겨진 의존성은 나쁘다

의존성 해결을 위한 방법으로 SERVICE LOCATOR 패턴이 있다. [Alur03]
이는 의존성을 해결할 객체들을 보관하는 일종의 저장소이다.

SERVICE LOCATOR 패턴은 서비스를 사용하는 코드로부터 서비스가 누구인지 어디에 있는지를 몰라도 되게 해준다 [Nystrom14]

SERVICE LOCATOR 패턴의 가장 큰 단점은 의존성을 감춘다는 점이다.
퍼블릭 인터페이스에 명시되어 있지 않은 의존성은 암시적이며 코드 깊은 곳에 숨어 있다는 의미가 된다.

숨겨진 의존성이 이해하기 어렵고 디버깅하기 어려운 이유는 문제점을 발견할 수 있는 시점을
코드 작성 시점이 아닌 실행 시점으로 미루기 때문이다.

의존성을 숨기는 코드는 단위 테스트 작성도 어렵다.

캡슐화는 코드를 읽고 이해하는 행위와 관련이 있다.
클래스의 퍼블릭 인터페이스만으로 사용 방법을 이해할 수 있는 코드가 캡슐화의 관점에서 훌륭한 코드다.

의존성을 구현 내부로 감추는 SERVICE LOCATOR는 캡슐화를 위반할 수 밖에 없다.

명시적인 의존성이 숨겨진 의존성 보다 좋다.
가급적 의존성을 객체의 퍼블릭 인터페이스에 노출하라.
의존성을 구현 내부에 숨기면 코드를 이해하고 수정하기 어려워진다.

04 의존성 역전 원칙

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

이를 의존성 역전 원칙(Dependency Inversion Principle, DIP)[Martin02]라고 부른다.

의존성 역전 원칙과 패키지

역전은 의존성의 방향뿐 아니라 인터페이스의 소유권에도 적용된다.

추상화 인터페이스는 클라이언트가 속한 패키지에 포함시키고
재사용될 일이 없는 클래스들은 별도의 독립적인 패키지에 모아야 한다.
마틴 파울러는 이 기법을 가리켜 SEPARATED INTERFACE 패턴[Fowler02]이라고 부른다.

유연하고 재사용 가능하며 컨텍스트에 독립적인 설계는 전통적인 패러다임이 고수하는 의존성의 방향을 역전시킨다.
전통적인 패러다임에서는 상위 수준 모듈이 하위 수준 모듈에 의존했다면
객체지향 패러다임에서는 상위 수준 모듈과 하위 수준 모듈이 모두 추상화에 의존한다.
따라서 인터페이스가 상위 수준 모듈에 속한다.

훌륭한 객체지향 설계를 위해서는 의존성을 역전시켜야 한다.

05 유연성에 대한 조언

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

설계의 미덕은 단순함과 명확함으로부터 나온다.
유연한 설계는 이와 다른데, 변경하기 쉽고 확장하기 쉬운 구조를 만들기 위해서는 단순함과 명확함의 미덕을 버리게 될 가능성이 높다.
유연한 설계라는 말의 이면에는 복잡한 설계라는 의미가 숨어 있다.

설계가 유연할수록 클래스 구조와 객체 구조 사이의 거리는 점점 멀어진다.
따라서 유연함은 단순성과 명확성의 희생 위에서 자라난다.
유연한 설계를 단순하고 명확하게 만드는 유일한 방법은 사람들 간의 긴밀한 커뮤니케이션뿐이다.

단순하고 명확한 해법이 만족스럽다면 유연성을 제거하라.
유연성으 코드를 읽는 사람들이 복잡함을 수용할 수 있을 때만 가치가 있다.

협력과 책임이 중요하다.

불필요한 SINGLETON 패턴[GOF94]은 객체 생성에 관해 너무 이른 시기에 고민하고 결정할 때 도입되는 경향이 있다.
객체를 생성하는 방법에 대한 결정은 모든 책임이 자리를 잡은 후 가장 마지막 시점에 내리는 것이 적절하다.

@jongfeel jongfeel added the GDG GDG Songdo and Incheon activity label Sep 22, 2024
@jongfeel jongfeel self-assigned this Sep 22, 2024
@jongfeel jongfeel changed the title 오브젝트, CHAPTER 09 유연한 설계 오브젝트, CHAPTER 09 유연한 설계, 2024-09-22 Sep 22, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
GDG GDG Songdo and Incheon activity
Projects
Status: Done
Development

No branches or pull requests

1 participant