Skip to content

Commit

Permalink
📝 chapter 8,9 정리
Browse files Browse the repository at this point in the history
  • Loading branch information
mintheon committed May 7, 2024
1 parent 859d284 commit 3bc4403
Show file tree
Hide file tree
Showing 2 changed files with 265 additions and 0 deletions.
133 changes: 133 additions & 0 deletions chapter08/minhyun/ch8.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
작고 응집도 높은 객체 → 책임의 초점이 명확하고 한 가지 일만 잘하는 객체

# 01. 의존성 이해하기

## 변경과 의존성

- **실행시점**
- 의존되는 객체가 정상적으로 동작하기 위해서 실행 시 의존 대상 객체가 반드시 존재해야 함.
- **구현 시점**
- 의존 대상 객체가 변경될 경우 의존 객체도 함께 변경됨.

객체가 예정된 작업을 정상적으로 수행하기 위해 **다른 객체를 필요로 하는 경우 두 객체 사이에 의존성이 존재한다고 말한다.**

- 의존성은 방향성을 가지며 항상 단방향이다.
- 의존성은 변경에 의한 영향의 전파 가능성을 암시한다.

## 의존성 전이

- 의존성이 실제로 전이될지 여부는 변경의 방향과 캡슐화의 정도에 따라 달라진다.
- 내부구현을 효과적으로 캡슐화되어 있다면 변경이 전파되지 않을 것.

- **직접 의존성**
- 한 요소가 다른 요소에 직접 의존하는 경우
- **간접 의존성**
- 직접적인 관계는 존재하지 않지만 의존성 전이에 의해 영향이 전파되는 경우

## 런타임 의존성과 컴파일 타임 의존성

- 런타임: 애플리케이션이 실행되는 시점
- 컴파일 타임: 작성된 코드를 컴파일 하는 시점 or 문맥에 따라선 코드 그 자체

***객체지향 애플리케이션에서…***

‘런타임’의 주인공은 ‘객체’

→ 런타임 의존성이 다루는 주제는 객체 사이의 의존성

‘컴파일’의 주인공은 ‘클래스’

→ 컴파일타임 의존성이 다루는 주제는 클래스 사이의 의존성

**유연하고 재사용 가능한 설계를 창조하기 위해선 동일한 소스코드 구조를 가지고 다양한 실행 구조를 만들 수 있어야 한다.**

- 어떤 클래스의 인스턴스가 다양한 클래스의 인스턴스와 협력하기 위해선 협력할 인스턴스의 구체적 클래스를 알아선 안된다. → 런타임에 해결해야 함.

## 컨텍스트 독립성

- 유연하고 확장 가능한 설계를 만들기 위해선 컴파일타임 의존성과 런타임 의존성이 달라야한다.
- 구체적인 클래스를 알수록 그 클래스가 사용되는 특정한 문맥에 강하게 결합되기 때문에 협력 객체의 구체적 클래스에 대해 알아선 안된다.

- 클래스가 사용될 특정한 문맥에 대해 최소한의 가정만으로 이뤄져 있다면 다른 문맥에서 재사용하기가 더 수월해지고 이를 ‘컨텍스트 독립성’이라고 한다.

## 의존성 해결하기

- 컴파일 타임 의존성을 실행 컨텍스트에 맞는 적절한 런타임 의존성으로 교체하는 것을 ‘의존성 해결’ 이라고 부른다. 일반적으로 3가지방법이 있다.
1. 객체를 생성하는 시점에 생성자를 통해 의존성 해결
2. 객체 생성 후 setter 메서드를 통해 의존성 해결
3. 메서드 실행 시 인자를 이용해 의존성 해결

# 02. 유연한 설계

## 의존성과 결합도

- 의존성은 ‘재사용성’과 관련이 있다.
- 컨텍스트에 독립적인 의존성은 바람직한 의존성임.
- 특정 컨텍스트에 강하게 결합된 의존성은 바람직하지 않은 의존성임.

→ 느슨하거나 약한 결합도를 가져야 한다.

## 지식이 결합을 낳는다.

- 서로에 대해 알고 있는 지식의 양이 결합도를 결정한다.
- 결합도를 느슨하게 만들기 위해선 협력 대상에 대해 필요한 정보 외엔 최대한 감추는 것이 중요하다. → 추상화

## 추상화에 의존하라

- **추상화**
- 어떤 양상, 세부사항, 구조를 좀 더 명확하게 이해하기 위해 특정 절차나 물체를 의도적으로 생략하거나 감춤으로써 복잡도를 극복하는 방법

- 추상화와 결합도의 관점에서 의존 대상을 다음과 같이 구분한다. (아래로 갈수록 느슨한 결합도를 가진다)
- 구체 클래스 의존성
- 추상 클래스 의존성
- 인터페이스 의존성

## 명시적인 의존성

- 의존성은 명시적으로 표현되어야 한다.
- 의존성을 구현 내부에 숨겨두지 말아라.
- 유연하고 재사용 가능한 설계란 퍼블릭 인터페이스를 통해 의존성이 명시적으로 드러나는 설계이다.
- 경계해야할 것은 의존성 자체가 아니라 의존성을 감추는 것이다 → 숨겨져 있는 의존성을 밝은 곳으로 드러내어 널리 알리자.

- 의존성이 명시적이지 않으면 의존성을 파악하기 위해 내부 구현을 직접 보아야함.

→ 고통스러움…


## new는 해롭다

- new 연산자를 사용시 구체 클래스의 이름을 직접 기술해야 함.
- 구체 클래스에 의존할 수 밖에 없기 떄문에 결합도가 높아짐.
- new 연산자는 어떤 인자를 이용해 클래스의 생성자를 호출해야 하는지도 알아야 함.
- 클라이언트가 알아야하는 지식의 양이 늘어나기 때문에 결합도가 높아짐.

**해결 방법은 인스턴스를 생성하는 로직과 생성된 인스턴스를 사용하는 로직을 분리하는 것.**

→ 생성의 책임을 클라이언트로 옮긴다.

***훌륭한 설계란..***

- 사용과 생성의 책임을 분리
- 의존성을 생성자에 명시적으로 드러냄
- 구체 클래스가 아닌 추상 클래스에 의존하게 함
- 객체를 생성하는 책임을 객체 내부가 아닌 클라이언트로 옮김

## 가끔은 생성해도 무방함

생성자를 체인처럼 연결하여 사용한다. (⇒ 오버로딩 할 때도 사용가능)

(첫번째 생성자에서 두번째 생성자를 ‘this’로 호출하여 구체 클래스를 다르게 삽입)

## 표준 클래스에 대한 의존은 해롭지 않다

의존성이 불편한 이유는 변경에 대한 영향을 암시하기 때문인데, 변경될 확률이 거의 없는 클래스라면 의존성이 문제가 되지 않는다.

(e.g. JDK에 포함된 표준 클래스)

해당 클래스들은 구체 클래스에 의존하거나 직접 인스턴스를 생성하더라도 문제가 없다.

---

- 유연하고 재사용 가능한 설계는 작은 객체들의 행동을 조합함으로써 새로운 행동을 이끌어 낼 수 있는 설계.
- 훌륭한 객체지향 설계란 객체가 어떻게 하는질 표현하는 것이 아니라 객체들의 조합을 선언적으로 표현함으로써 객체들이 무엇을 하는지 표현하는 설계.
- 이런 설계를 창조하는데 핵심은 의존성을 관리하는 것.
132 changes: 132 additions & 0 deletions chapter09/minhyun/ch9.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
# 01. 개방-폐쇄 원칙(Open Close Principle)

소프트웨어 개체는 확장에 대해 열려 있어야 하고, 수정에 대해서는 닫혀 있어야 한다.

‘확장’ → 동작

‘수정’ → 코드

- **확장에 대해 열려있다**
- 요구사항 변경시 해당 변경에 맞게 새로운 ‘동작’을추가하여 애플리케이션의 기능 확장
-**정에 대해 닫혀있다.**
- 기존의 ‘코드’를 수정하지 않고도 동작을 추가하거나 변경 가능

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

앞에서 말한 런타임 의존성과 컴파일타임 의존성에 관련된 이야기이다.

앞쪽에서 진행했던 `OverlappedDiscountPolicy` , `NoneDiscountPolicy`의 구현처럼 기존 코드는 전혀 손대지 않은 채 클래스를 추가함으로써 동작 확장을 하였다.

## 추상화가 핵심이다

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

- 상속을 통해 생략된 부분을 구체화함으로써 할인 정책을 확장할 수 있다.
- 변하지 않는 부분을 고정하고 변하는 부분을 생략하는 추상화 메커니즘이 개방-폐쇄 원칙의 기반이 된다.
- 개방-폐쇄 원칙에서 폐쇄를 가능하게 하는 것은 의존성의 방향이다.

## 02. 생성 사용 분리

- 동일 클래스 안에서 객체 생성과 사용이라는 두가지 이질적인 목적을 가진 코드가 공존하는 것은 문제이다.

→ 객체에 대한 생성과 사용을 분리 해야 한다.

→ 객체를 생성할 책임을 클라이언트로 옮기는 것.


금액 할인 정책을 적용할지, 비율 할인 정책을 적용할 지를 알고 있는 것은 그 시점에 Movie와 협력할 클라이언트이기 때문에 조금만 생각해보면 이 방법이 타당한 걸 알 수 있다.

→ 컨텍스트에 관한 결정권을 가지고 있는 클라이언트로 컨텍스트에 대한 지식을 옮김으로써 특정한 클라이언트에 결합되지 않고 독립적일 수 있다.

## Factory 추가하기

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

- 책임 할당의 기본 원칙은 책임을 수행하는데 필요한 정보를 가장 많이 알고있는 ‘INFORMATION EXPERT’에게 책임을 할당하는 것.

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

**모든 책임을 도메인 객체에게 할당하면 낮은 응집도, 높은 결합도, 재사용성 저하와 같은 심각한 문제점에 봉착하게 될 가능성이 높아진다.**

이 경우 도메인 개념을 표현한 객체가 아닌 설계자가 **편의를 위해 임의로 만들어낸 가공의 객체에게 책임을 할당하여 문제를 해결**해야 함

**PURE FABRICATION(순수한 가공물)**

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

→ 특정한 행동을 표현하는게 일반적이며, 행위적 분해에 의해 생성되는 것이 일반적.

# 03. 의존성 주입

외부의 독립적인 객체가 인스턴스를 생성한 후 이를 전달해서 의존성을 해결하는 방법.

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

## 숨겨진 의존성은 나쁘다

널리 사용되는 방법은 ‘SERVICE LOCATOR 패턴’ 이다.

→ 객체가 직접 ‘service locator’에게 의존성을 해결해 줄 것을 요청함 (의존성 주입과 반대)

필자는 해당 패턴을 선호하지 않는데 이유는 ‘의존성을 감추기 때문’ 이라고 한다.

- 해당 문제가 컴파일타임이 아닌 런타임에 가서야 발견이 된다.
- 이해가 어렵고 디버깅이 어려운 이유는 코드 작성 시점이 아닌 실행 시점으로 문제 발견 시점을 미루기 때문.
- 의존성의 대상을 설정하는 시점과 의존성이 해결되는 시점을 멀리 떨어뜨려 놓음.
- 내부 정적변수를 사용해 객체를 관리하여 모든 단위 테스트가 locator 상태를 공유하게 되어, 단위 테스트가 고립되어야 한다는 기본 원칙이 위반됨.
- 캡슐화가 위반됨.
- 의존성을 이해하기 위해 내부 구현을 이해할 것을 강요함.

어쩔 수 없이 해당 패턴을 사용해야 하는 경우도 있다.

- 의존성 주입을 지원하는 프레임워크를 사용하지 못하는 경우
- 깊은 호출 계층에 걸쳐 동일한 객체를 계속해서 전달해야 하는 고통을 견디기 어려운 경우

**가능한 의존성을 명시적으로 표현할 수 있는 기법을 사용하라.**

# 04. 의존성 역전 원칙

- 협력에서 중요한 정책이나 의사결정, 비즈니스의 본질을 담고 있는 것은 상위 수준의 클래스다.
- 상위 수준의 클래스는 하위 클래스에 의존해선 안된다.

**이걸 해결하기 위한 만능 열쇠는 ‘추상화’~**

- **의존성 역전원칙 (Dependency Inversion Principle)**
- 상위 수준의 모듈은 하위 수준의 모듈에 의존해선 안된다. 둘 모두 추상화에 의존해야 한다.
- 추상화는 구체적인 사항에 의존해선 안된다. 구체적인 사항은 추상화에 의존해야 한다.

## 의존성 역전 원칙과 패키지

추상화를 별도의 독립적인 패키지가 아닌 클라이언트가 속한 패키지에 포함시켜야 한다. 그리고 재사용 될 필요가 없는 클래스들은 별도의 독립적인 패키지에 모아야 한다.

**→ SEPARATED INTERFACE 패턴**

잘 설계된 객체지향 애플리케이션은 인터페이스의 소유권을 서버가 아닌 클라이언트에 위치시킨다.

- 전통적인 패러다임
- 상위수준 모듈이 하위수준 모듈에 의존
- 인터페이스가 하위 수준 모듈에 속함
- 객체지향 패러다임
- 상위수준 모듈과 하위 수준 모듈이 모두 추상화에 의존
- 인터페이스가 상위수준 모듈에 속함

# 05. 유연성에 대한 조언

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

- 유연한 설계 == 복잡한 설계
- 미래에 변경이 일어날지도 모른다는 막연한 불안감은 불필요하게 복잡한 설계를 낳는다. **아직 일어나지 않은 변경은 변경이 아니다.**
- 유연성은 항상 복잡성을 수반한다.
- 유연하지 않은 설계는 단순하고 명확함.
- 유연한 설께는 복잡하고 암시적.

## 협력과 책임이 중요하다.

- 설계를 유연하게 만들기 위해선 협력에 참여하는 객체가 다른 객체에게 어떤 메시지를 전송하는지가 중요하다.
- 역할, 책임, 협력에 초점을 맞춰야 한다.
- 객체를 생성할 책임을 담당할 객체나 객체 생성 메커니즘을 결정하는 시점은 책임 할당의 마지막 단계로 미뤄야만 한다.

0 comments on commit 3bc4403

Please sign in to comment.