[오브젝트] 부록 B 타입 계층의 구현

타입과 타입 계층을 구현할 수 있는 방법들을 정리하자.

클래스를 이용한 타입 계층 구현

타입은 객체의 퍼블릭 인터페이스이다. 클래스는 객체의 타입과 구현을 동시에 정의한 것이다.

Phone 클래스가 있다. Phone 의 인스턴스는 calculateFee 메시지를 수신할 수 있는 퍼블릭 메서드를 구현한다. 타입은 퍼블릭 인터페이스이기 때문에, Phone 클래스는 calculateFee 메세지에 응답할 수 있는 타입을 선언한 동시에 객체 구현을 정의한 것이다.

상속은, 퍼블릭 인터페이스는 유지하면서 새로운 구현을 가진 객체를 추가할 수 있는 간단한 방법이다. 하지만, 상속은 자식 클래스를 부모 클래스의 구현에 강하게 결합시킨다.

인터페이스를 이용한 타입 계층 구현

인터페이스는, 상속으로 인한 결합도 문제를 피하고 다중 상속이라는 구현 제약을 해결할 수 있는 방법이다.

추상 클래스를 이용한 타입 계층 구현

추상 클래스는, 클래스 상속을 이용해 구현을 공유하면서도 결합도로 인한 부작용을 피하는 방법이다.

추상 클래스와 인터페이스 결합하기
Read more

[오브젝트] 부록 C 동적인 협력, 정적인 코드

좋은 설계는 객체 사이의 협력과 행동을 표현하는 동적 모델을 기반으로 해야한다.

  1. 동적 모델
    프로그램 실행 구조를 표현하는 움직이는 모델

  2. 정적 모델
    코드의 구조를 담는 고정된 모델

01 동적 모델과 정적 모델

행동이 코드를 결정한다

객체가 외부에 제공하는 행동이 중요하다. 동적 모델이 정적 모델을 결정해야한다.

02 도메인 모델과 구현

도메인 모델에 관하여
  1. 도메인
    사용자가 프로그램을 사용하는 대상 영역

  2. 모델
    지식을 선택적으로 단순화하고 의식적으로 구조화한 형태

  3. 도메인 모델
    사용자가 프로그램을 사용하는 대상 영역에 대한 지식을 선택적으로 단순화하고 의식적으로 구조화한 형태

소프트웨어의 도메인에 대해 고민하고 도메인 모델을 기반으로 소프트웨어를 구축해야한다. 그러면, 개념과 소프트웨어 사이의 표현적 차이를 줄일 수 있기 때문에 이해하고 수정하기 쉬온 소프트웨어를 만들 수 있다.

중요한 것은, 도메인 모델을 작성하는 것이 목표가 아니라 출발점이다. 중요한 것은 객체들의 협력을 지원하는 코드 구조를 만드는 것이다. 코드의 구조를 주도하는 것은 행동이다.

Read more

[오브젝트] 부록 A 계약에 의한 설계

인터페이스만으로 객체의 행동에 관한 다양한 관점을 전달하기 어렵다.
명령의 부수효과를 쉽고 명확하게 표현할 수 있는 커뮤니케이션 수단이, 계약에 의한 설계이다.
여기서 중요한 것은 코드가 아니라, 개념이다.

01 협력과 계약

부수 효과를 명시적으로

일반적인 정합성 체크 로직은 코드의 구현 내부에 숨겨져있어 실제로 코드를 분석하지 않는 한 정확하게 파악하기 어렵다.

하지만, Code Contracts 와 같이 계약에 의한 설계를 지원하는 라이브러리나 언어들은 일반 로직과 구분할 수 있도록 제약 조건을 명시적으로 표현하는 것이 가능하다.

02 계약에 의한 설계

버트란드 마이어가 제시한 계약은, 사람들 사이의 계약과 유사하다. 계약은 협력에 참여하는 두 객체 사이의 의무와 이익을 문서화한 것이다.

  1. 협력에 참여하는 객체는 계약으로부터 이익을 기대하고 이익을 얻기위해 의무를 이행한다.
  2. 협력에 참여하는 객체의 이익과 의무는 객체의 인터페이스 상에 문서화된다.

의도를 드러내는 인터페이스는 오퍼레이션의 시그니처만으로 어느 정도 클라이언트와 서버가 협력을 위해 수행해야하는 제약 조건을 명시한다.

계약은 여기서 한 걸음 더 나아간다. 서버는 자신이 처리할 수 있는 범위의 값들을 클라이언트가 전달할 것으로 기대한다. 클아이언트는 자신이 원하는 값을 서버가 반환할 것이라고 예상한다. 클아이언트는 메세지 전송 전과 후의 서버 상태가 정상일 것이라고 기대한다.

Read more

[오브젝트] 15장_디자인 패턴과 프레임워크

  1. 디자인 패턴
    특정한 변경을 일관성있게 다룰 수 있는 협력 텝플릿 제공한다.
    설계를 재사용하는 것이 목적이다.

  2. 프레임워크
    특정한 변경을 일관성 있게 다룰 수 있는 확장 가능한 코드 템플릿을 제공한다.
    설계와 코드를 함께 재사용하기 위한 것이 목적이다.

디자인 패턴과 설계 재사용

소프트웨어 패턴

패턴의 특징은,

  1. 반복적으로 발생하는 문제와 해법의 쌍으로 정의된다.
  2. 이미 알려진 문제와 이에 대한 해법을 문서로 정리할 수 있고, 다른 사람과 의사소통 가능하다.
  3. 추상적인 원칙과 실제 코드 작성 사이의 간극을 메워준다. 실질적인 코드 작성을 돕는다.
  4. 패턴은 실무에서 탄생했다.

마틴 파울러에 의하면, 패턴은 하나의 실무 컨텍스트에서 유용하게 사용해왔고 다른 실무 컨텍스트에도 유용할 것이라고 예상되는 아이디어다.
프로젝트 조직을 구성하는 방법, 프로젝트 일정을 추정하는 방법 등 반복적인 규칙을 발견할 수 있는 모든 영역이 패턴의 대상이다.

패턴 분류

  1. 디자인 패턴
    일반적인 설계 문제를 해결한다.
    협력하는 컴포넌트들 사이에서 반복적으로 발생하는 구조를 서술한다.

  2. 아키텍쳐 패턴
    디자인 패턴의 상위에 있다.
    소프트웨어의 전체적인 구조를 결정한다.

  3. 이디엄
    디자인 패턴의 하위에 있다.
    특정 프로그래밍 언어에만 국한된 하위 레벨 패턴이다.
    예를 들어, C++ 의 COUNT POINT 이디엄은 자바에서는 유용하지 않다.

  4. 분석 패턴
    도메인 내의 개념적인 문제를 해결한다.

패턴과 책임-주도 설계

객체지향 설계에서 중요한 일을 다시 정리해보자.
바로, 올바른 책임을 올바른 객체에게 할당하고 객체 간의 유연한 협력 관계를 구축하는 것이다.

Read more

[오브젝트] 14장_일관성 있는 협력

핸드폰 과금 시스템 변경하기

기본 정책 확장

  1. 고정 요금 방식 (ex) 10초당 18원
  2. 시간대별 방식 (ex) 00시-19시 : 10초당 19월, 19시-24시 : 10초당 15원
  3. 요일별 방식 (ex) 평일 : 10초당 38원, 공휴일 : 10초당 19원
  4. 구간별 방식 (ex) 초기 1분 : 10초당 50원, 초기 1분 이후 : 10초당 20원

고정 요금 방식 구현하기

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class FixedFeePolicy extends BasicRatePolicy {
private Money amount;
private Duration duration;

public FixedFeePolicy(Money amount, Duration duration) {
this.amount = amount;
this.duration = duration;
}

@Override
protected Money calculateCallFee(Call call) {
return amount.times(call.getDuration().getSeconds() / seconds.getSeconds());
}
}

시간대별 방식 구현하기

아래 클래스에서 가장 중요한 것은, 시간에 따라 서로 다른 요금 규칙을 정의하는 방법을 결정하는 것이다.
이를 위해 서로 다른 List 를 가질 수 있다.
같은 규칙에 포함된 요소들은 List 의 동일한 인덱스에 위치한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
public class TimeOfDayDiscountPolicy extends BasicRatePolicy {
private List<LocalTime> starts = new ArrayList<>();
private List<LocalTime> ends = new ArrayList<>();
private List<LocalTime> duration = new ArrayList<>();
private List<Money> amounts = new ArrayList<>();

@Override
protected Money calculateCallFee(Call call) {
...
}

...
}

요일별 방식 구현하기

Read more

[오브젝트] 13장_서브클래싱과 서브타이핑

상속의 두가지 용도는 다음과 같다.

  1. 타입 계층 구현

    동일한 메세지에 대해 서로 다르게 행동할 수 있는 다형적인 객체를 구현하기 위해서는 객체의 행동을 기반으로 타입 계층을 구성해야한다.

  2. 코드 재사용

    부모 클래스와 자식 클래스가 강하게 결합되기 때문에 변경하기 어려운 코드를 얻게 된다.

01 타입

타입을 세 가지 관점으로 정리하자.

개념 관점의 타입
  1. 타입

    우리가 인지하는 세상의 사물의 종류를 의미한다.

    자바, 루비, C 를 프로그래밍 언어로가 부를 때, 이것들을 프로그래밍 언어라는 타입으로 분류하고 있는 것이다.

  2. 인스턴스

    어떤 대상이 타입으로 분류될 때 그 대상을 타입의 인스턴스라고 한다.

    자바, 루비, C 는 프로그래밍 언어의 인스턴스이다.

프로그래밍 언어 관점의 타입

하드웨어는 데이터를 0과 1로 구성된 일련의 비트 조합으로 취급한다.

프로그래밍 언어 관점의 타입은, 비트 묶음에 의미를 부여하기 위해 정의된 제약과 규칙을 의미한다.

타입은 두가지 목적으로 사용된다.

Read more

[오브젝트] 12장_다형성

상속의 진정한 목적은 코드 재사용이 아니라 다형성을 위한 서브타입 계층을 구축 하는 것이다.

상속의 관점에서 다형성이 구현되는 기술적인 메커니즘을 정리한다.

01 다형성

다형성(Polymorphism) 은 다음 둘의 합성어이다. 즉, 많은 형태를 가질 수 있는 능력이다.

  • ploy : 많은
  • morph : 형태

다형성은 다음과 같이 분류될 수 있다. 이번장은 포함 다형성에 대해 다룬다.

  • 유니버셜 다형성

    • 매개변수 다형성

      클래스의 인스턴스 변수나 메서드의 매개변수 타입을 임의의 타입으로 선언한 후 사용하는 시점에 구체적인 타입으로 지정하는 방식.

      ex) List 인터페이스는 컬렉션에 보관할 요소의 타입을 임의의 타입 T로 지정하고 있으며 실제 인스턴스를 생성하는 시점에 T 를 구체적인 타입으로 지정

    • 포함 다형성 (서브타입 다형성)

      메세지가 동일해도 수신한 객체의 타입에 따라 실제로 수행되는 행동이 달라지는 능력.

  • 임시 다형성

    • 오버로딩 다형성

      하나의 클래스 안에 동일한 이름의 메서드가 존재하는 경우.

    • 강제 다형성

      자동적인 타입 변환이나 사용자가 직접 구현한 타입 변환을 이용해 동일한 연산자를 다양한 타입에 사용할수 있는 방식.

      ex) 이항 연산자인 ‘+’ 는 피연산자가 하나는 정수형이고 다른 하나는 문자열인 경우, 정수형 피연산자는 문자열 타입으로 강제 형변환

02 상속의 양면성

  1. 데이터 관점의 상속

    부모 클래스에서 정의한 모든 데이터를 자식 클래스의 인스턴스에 자동으로 포함한다.

  2. 행동 관점의 상속

    데이터뿐만 아니라 부모 클래스에서 정의한 일부 메서드 역시 자동으로 자식 클래스에 포함한다.

    외부의 객체가 부모 클래스의 인스턴스에 전송할 수 있는 모든 메세지는 자식 클래스의 인스턴스에도 전송할 수 있다.

상속을 사용한 강의 평가
Read more

[오브젝트] 11장_합성과 유연한 설계

코드 재사용 기법으로는,

  1. 상속
    부모클래스와 자식클래스를 연결해서, 부모클래스의 코드 재사용

  2. 합성
    전체를 표현하는 객체가 부분을 표현하는 객체를 포함해서 객체의 코드 재사용
    내부에 포함되는 객체의 구현이 아니라 퍼블릭 인터페이스에 의존

01 상속을 합성으로 변경하기

상속의 문제는,

  1. 불필요한 인터페이스 상속
  2. 메서드 오버라이딩 오작용
  3. 부모 클래스와 자식 클래스 동시 수정

이 문제들을 합성으로 해결해보자.

불필요한 인터페이스 상속 : java.utils.Properties, java.utils.Stack
  1. 불필요한 Hasbtable 의 오퍼레이션들이 Properties 클래스의 퍼블릭 인터페이스를 오염시키지 않는다.
1
2
3
4
5
6
7
8
9
10
11
public class Properties {
private Hashtable<String, String> properties = new Hashtable<>();

public String setProperty(String key, String value) {
return properties.put(key, value);
}

public String getProperty(String key) {
return properties.get(key);
}
}
  1. 불필요한 Vector 의 오퍼레이션들이 Stack 클래스의 퍼블릭 인터페이스를 오염시키지 않는다.
Read more

[오브젝트] 10장_상속과 코드 재사용

이번 장은, 클래스 재사용을 위해 새로운 클래스를 추가하는 가장 대표적 기법인 상속에 대해 정리한다.

01 상속과 중복 코드

DRY 원칙 (Don’t Repeat Yourself)

“동일한 지식을 중복하지 마라”
중복 여부를 판단하는 기준은 변경이다. 중복 코드는 변경을 방해한다. 이것이 중복 코드를 제거해야하는 핵심적 이유이다.
중복 코드는 코드를 수정하는데 노력을 몇배로 증가시킨다. 왜냐하면,

  1. 어떤 코드가 중복인지 찾아야하고,
  2. 찾아낸 모든 코드를 일관되게 수정해야하고,
  3. 개별적으로 테스트를 다 해야하기 때문이다.
중복과 변경

한 달에 한 번씩 가입자별로 전화 요금 계산하는 간단한 애플리케이션을 개발해보자.
개별 통화 기간을 저장하는 클래스가 필요하다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class Call {
private LocalDateTime from; //통화 시작 시간
private LocalDateTime to; // 통화 종료 시간

public Call(LocalDateTime from, LocalDateTime to) {
this.from = from;
this.to = to;
}

public Duration getDuration(){
return Duration.between(from, to);
}

public LocalDateTime getFrom(){
return from;
}
}

전체 통화 목록에 대해 알고 있는 정보 전문가에게 요금 계산 책임 을 할당해야한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
public class Phone {
private Money amount;
private Duration seconds;
private List<Call> calls = new ArrayList<>();

public Phone(Money amount, Duration seconds) {
this.amount = amount;
this.seconds = seconds;
}

public void call(Call call){
calls.add(call);
}

public List<Call> getCalls() {
return calls;
}

public Money getAmount() {
return amount;
}

public Duration getSeconds() {
return seconds;
}

public Money calculateFee(){
Money result = Money.ZERO;

for (Call call : calls){
result = result.plus(amount.times(call.getDuration().getSeconds() / seconds.getSeconds()));
}

return result;
}
}
Read more

[오브젝트] 9장_유연한 설계

이번 장은, 8장에서 설명한 의존성 관리 기법들을 원칙이라는 관점에서 정리한다.

01 개팡-폐쇄 원칙

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

  1. 확장에 열려 있다
    요구사항이 변경될 때, 변경에 맞게 새로운 동작을 추가해서 애플리케이션의 기능을 확장할 수 있다.

  2. 수정에 닫혀 있다
    기존의 코드를 수정하지 않고 애플리케이션의 동작을 추가하거나 변경할 수 있다.

컴파일타임 의존성을 고정시키고 런타임 의존성을 변경하라
  1. 런타임 의존성
    실행 시에 협력에 참여하는 객체들 사이의 관계

  2. 컴파일 타임 의존성
    코드에서 드러나는 클래스들 사이의 관계

중복 할인 정책을 추가하기 위해 한 일은, DiscountPolicy 의 자식 클래스로 OverlappedDiscountPolicy 클래스를 추가한 것이다. 단순히 새로운 크래스를 추가하는 것만으로 Movie 를 새로운 컨텍스트에 사용되도록 확장할 수 있었다.
이 설계 방식은,

  1. 새로운 할인 정책을 추가해서 새로운 기능을 확장할 수 있도록 허용한다. 즉, 확장에 열려 있다.
  2. 기존의 코드를 수정할 필요 없이 새로운 클래스 추가만으로 새로운 할인 정책을 확장한다. 즉, 수정에 닫혀 있다.

의존성 관점에서 개방-폐쇄 원칙을 따르는 설계란, 컴파일 타임 의존성은 유지하면서 런타임 의존성의 가능성을 확장하고 수정할 수 있는 구조이다.

추상화가 핵심이다
Read more