- 소프트웨어를 설계할 때 저자들이 추구하는 가치, 달성하고자 하는 결과물을 소개
- 다음 장에서 "TDD를 이용해서" 위의 가치를 달성하는 방법을 보여줄 것
우리는 작성하기 쉬운 코드보다는 유지보수하기 쉬운 코드를 높게 평가한다.
- SI 개발자, 불나방이 되거나 자토이치가 되거나 by 황건구님 "복붙방식의 개발로는 회사도, 개인도 성장할 수 없다"
- 소프트웨어 설계할 때 받는 챌린지
- 미래를 어떻게 예측할 것인가?
- 현재의 요구사항만 구현 vs 미래를 예측하여 구현
- 앞으로의 확장을 고려하여 설계할 것인가?
- 더 작은 단위로 나누고, 각 단위에 의미를 부여한다
기능을 객체로, 객체를 패키지로, 패키지를 프로그램으로, 프로그램을 시스템으로 구조화
- 관심사: 기능, 행동, 목적
- e.g. MVC, MVP, Layered Architecture
The essence of abstractions is preserving information that is relevant in a given context, and forgetting information that is irrelevant in that context. – John V. Guttag[1]
- 콕번의 포트와 어댑터, 에릭 에반스의 손상 방지 계층(Anti-Corruption Layer)
- 데이터나 프로세스 등을 의미가 비슷한 개념이나 의미 있는 표현으로 정의하는 과정
- 특정한 성질 e.g. Money: amount, currency
- 공통 성질(일반화) e.g. Gpu: GForce, Radeon
+---------------------+
| uploadFileViaScp() | --+
+---------------------+ |
+---------------------+ | +-------------------+
| sendDateViaHttp() | +--> | sendPushMessage |
+---------------------+ | +-------------------+
+---------------------+ |
| insertDataIntoTbl() | --+
+---------------------+
- 타입 추상화, 추상 타입을 이용한 프로그래밍은 내부 구현을 감춤으로서, 기능의 구현이 아닌 의도를 드러냄
+---------------------+ +---------------------+
| OrderCancelService | ---> | <<I>> Notifier | <|- Concretes
+---------------------+ | +---------------------+
| +cancel(onum: Long) | | +---------------------+
+---------------------+ +-> |<<I>> NotifierFactory| <|- Concretes
+---------------------+
public void cancel(String orderNumber) {
// 주문 취소 처리
// 푸쉬를 보내는 로직이 아니라, "주문 취소"라는 본질에 집중할 수 있고,
// 푸쉬 메시지를 보내는 정책을 getNotifier()로 캡슐화함으로써 정책 변경에 따른 영향을 최소화할 수 있음
Notifier notifier = NotifierFactory.instance().getNotifier();
notifier.notify(order.toNotification());
}- 추상화는 정책이 변경/확장으로 인해 추상화가 필요할 때, 그 때 시도할 것
- 고수준 vs 저수준
| 고수준 | 저수준 |
|---|---|
| 도면 이미지를 저장하고 | NAS에 이미지를 저장한다 |
| 측정 정보를 저장하고 | foo 테이블에 저장한다 |
- 고수준이 저수준에 직접 의존하면? 저수준 모듈의 변경이 고수준 모듈에 영향을 미치게 됨. e.g. 도면 이미지를 저장한다는 고수준 정책은 변경되지 않았으나, NAS 대신 S3에 저장한다는 저수준 요구사항 변경으로 인해 고수준 코드의 변경이 필요하게 됨
// 올바른 설계
+----------------------+
| MeasureService |
+----------------------+
↓
+----------------------+
| <<I>> StorageService |
+----------------------+
고수준 모듈
------------------------------------
↑ 저수준 모듈
+----------------------+
| NasStorage |
+----------------------+ +
| AwsS3Storage |
+----------------------+
- "데이터"와 "관련 기능"을 묶는 것
- 캡슐화는 의도(비즈니스 요구사항)에 대한 이해를 높임. e.g.
hasRegularPermission()은 정회원에 대한 정책이구나... - 캡슐화는 객체의 행위가 해당 객체의 API를 통해서만 좌우될 수 있음을 보장한다
if (acc.hasRegularPermission()) {
// 정회원 기능 ...
}
public class Account {
private Membership membership;
private Date expDate;
public boolean hasRegularPermission() {
// 요구사항이 변경되면, 아래 표현식을 변경하면 됨
return membership == REGULAR && expDate.isAfter(now());
}
}- 객체가 해당 객체의 기능을 구현하는 방법을 추상화된 API 너머로 감춘다
- 정보 은닉을 통해 관련이 없는 낮은 수준의 세부 사항을 알 필요 없이 더 높은 수준의 추상화를 활용할 수 있다
- https://en.wikipedia.org/wiki/Encapsulation_(computer_programming)
- 객체에서 중요한 것은 API를 통해 해당 객체의 내부에 접근하는 것을 캡슐화하고, 세부 사항을 시스템의 나머지 부분으로부터 감추는 것이다.
- 시스템에서 한 객체는 다른 객체와 메시지를 주고받으며 의사소통한다. 한 객체와 직접 의사소통하는 객체를 해당 객체의 **이웃(peer)**이라 한다.
// 나쁜 코드
((EditSaveCustomizer) master.getModelisable().getDockablePanel().getCustomer())
.getSaveItem()
.setEnabled(Boolean.FALSE.booleanValue());- 한 객체의 역할이 원지 설명할 때는 접속사('and', 'or')를 쓰지 않고도 해당 객체의 역할을 설명할 수 있어야 한다
우리는 이웃하는 객체와의 관계를 (대략) 세 가지 유형으로 나눈다
- 모듈 A가 모듈 B가 없으면 정상 작동하지 않는다면, "A가 B에 의존한다"고 말한다
- 직접적 의존은 아니지만, 알림의 대상이 되는 이웃
- 관찰자 패턴 참조
- Factory등 컨텍스트에 맞는 이웃을 제공하는 또 다른 이웃
// https://en.wikipedia.org/wiki/Factory_(object-oriented_programming)
public class ImageReaderFactory {
public static ImageReader createImageReader(ImageInputStreamProcessor iisp) {
if (iisp.isGIF()) {
return new GifReader(iisp.getInputStream());
}
else if (iisp.isJPEG()) {
return new JpegReader(iisp.getInputStream());
}
else {
throw new IllegalArgumentException("Unknown image type.");
}
}
}- New or new not. There is not try. - Yoda
- 객체는 생성과 동시에 완벽한 상태가 되는 것이 가장 좋다. NullPointer를 피하려면 널 객체 패턴
- 복합 객체의 API는 구성 요소의 존재와 구성 요소 간의 상호 작용을 감추고 더 단순한 추상화를 이웃에게 드러내야 한다
- 기계식 시계를 생각해보면 시계는 시간을 표시하는 두세개의 침과 시간을 조정하는데 쓰는 태엽 감는 꼭지가 있지만, 동작하는 부품은 모두 한데 조립되어 있다
- 복합 객체의 API는 구성 요소의 API보다 복잡해서는 안된다
// Worst code
moneyEditor.getAmoundField().setText(String.valueOf(money.amount()));
moneyEditor.getCurrencyField().setText(money.currencyCode());// Bad code
moneyEditor.setAmountField(money.amount());
moneyEditor.setCurrencyField(money.currencyCode());// Better ...
moneyEditor.setValue(money);
public MoneyEditor {
private String amount;
private String currency;
public setValue(Money money) {
amount = money.amount();
currency = money.currencyCode();
}
}- 시스템을 구성하는 객체가 콘텍스트 독립적(context-independent)이라면 해당 시스템은 변경하기가 쉽다
- 콘텍스트 독립적이라는 말은 각 객체가 해당 객체를 실행하는 시스템에 관해 아무것도 알지 못한다는 의미다
- 캡슐화는 늘 하면 좋긴 하지만 때로는 정보를 잘못된 곳에 감추는 일이 생길수 있다
- "캐시에 대한 자료 구조를 `CachingAuctionLoader` 클래스에 캡슐화한다"
- "애플리케이션 로그 파일의 이름을 `PricingPolicy` 클래스에 캡슐화한다"
- "캐시에 사용되는 자료 구조를 `CachingAuctionLoader` 클래스에 감춘다"
- "애플리케이션 로그 파일의 이름을 PricingPolicy 클래스에 감춘다"

