본문 바로가기

객체지향설계

코드스피츠 2강 오브젝트 - 2회차(1)

책 오브젝트 13장를 기반으로 하는 코드스피츠 강의 오브젝트2 - 2회차 (1) 를 정리한 내용입니다.

 

1. 코드스피츠 2강 오브젝트 - 1회차

2. 코드스피츠 2강 오브젝트 - 2회차(1)

3.코드스피츠 2강 오브젝트 - 2회차(2)

4.코드스피츠 2강 오브젝트 - 3회차

5.코드스피츠 2강 오브젝트 - 4회차

6.코드스피츠 2강 오브젝트 - 5회차

7.코드스피츠 2강 오브젝트 - 6회차 (Final)

 

 

 

계약 (Contract)

프로그램에서는 메시지를 통해서 계약을 성립하게 된다.



전달받은 메시지의 규격을 지키고 있어야 한다. (precondition)

Receiver 가 메시지를 검증하는 것을 기본적으로 정책으로 삼고있다.

 

전달할 메시지의 규격 (postcondition)

Receiver 가 값을 돌려주기 전에 한다.

 

객체 자신의 규격 (class invariant)

예를 들면 네트워크 Bridge 를 들수있다.

메시지를 받을 수 있는 필드가 선언되어 있는가? 

 

위임된 책임의 컨텍스트

컨텍스트를 잘 맞춰주고 있는가?

 

객체간의 서로 계약을 하기 위해선 위 4가지가 만족되어 있어야 한다. 

 

Invariant

객체 초기 상태, 객체의 메시지 수신전 상태라고 볼 수 있다.

 

메시지와 무관한 객체의 상태 (필드)

일반적으로 피드값의 상태 점검

DI 에게 위임하거나 초기화 할당으로 처리

 

 

public class Plan {
    // Calculator calc; 는 초기값이 Null 이다.
    private Calculator calc;
    private Set<Call> calls = new HashSet<>();
    public final void addCall(Call call){
        calls.add(call);
    }
    public final void setCalculator(Calculator calc){
        this.calc = calc;
    }
    public final Money calculateFee(){
        // calc 은 항상 Null 이 아닌지 ?
        return calc.calcCallFee(calls, Money.ZERO);
    }
}

 

Calculator 생성자 조건이 없기 때문에 Null을 방지하기 위해서 기본 객체를 생성해 줄수 있다. 

 

 

 

Precondition

일반적으로 validation

메시지로 받는 값을 스스로 검증함

검증이 확정된 형으로 갈음할 수 있음

 

Null Precondition

빨간색 박스는 Null 문제가 발생할 수 있다.

 

@NonNull Annotation

은 Compile 타임에서는 검증할 수 있으나 Runtime 타임에서는 알 수 없다. 

 

if  Null 이 아닌 경우를 체크한다.

이 경우에는 returnvoid 가 아니라 boolean 이어야 한다.

return true = 성공

return false = 실패

 

Context 상에 부드럽게 넘어도가도 되는 것인지 알아야 한다. 

 

throw

하나를 throw 로 던지면 모든 것들은 다 throw 던지는 정책을 팀내 정책, 회사 정책을 세워라!

 

추가적인 상태 검사

calc 가 비어있는지도 체크할 수 있다. 

 

 

Postcondition

일반적으로 결과값 검증이라 함

보내줄 값이 올바름을 검증함

검증이 확정된 형으로 갈음할 수 있음

 

 

사후 검증 대상

0원 혹시 음수가 나오면 정상이 아니다. 

 

public class Plan{
    private Calculator calc = new Calculator();
    private Set<Call> calls = new HashSet<>();
    public final void addCall(Call call){...}
    public final void setCalculator(Calculator calc){...}
    public final Money calculateFee(){
        Money result = calc.calcCallFee(calls, Money.ZERO);
        if(calls.size() > 0 && (
                result.equals(Money.ZERO) ||
                        result.isLessThan(Money.ZERO)
        )) throw new RuntimeException("calculate error");
        return result;
    }
}

Call 이 최소 1건이 있었는데 요금이 0원이거나 음수인지 아닌지를 검증한다.

 

계약별 책임할당 

Null 체크와 같은 것은 어쩔수없이 본인이 해야한다. 

 

isEmptygetter 나 마찬가지 역할을 한다. 

이것은 캡슐화하는데 실패했기 때문에 일어난다. 

 

헐리우드 원칙 위반 (묻지 말고 시켜라) 하고 있다.

 

Plan Calculator에게 calls 의 계산 로직의 책임을 위임하고 있기 때문에 

Calculator 에게 시키는 것이 바람직하다. (헐리우드 원칙)

 

사후계약조건 책임할당

PlanMoneyresult를 검사해야할 이유가 전혀 없다.

this 가 없기 때문에 빨간색 박스Plan 의 메소드가 아니다.

 

 

Calculator 는 calc 계산하는 로직을 위임받은 객체이므로 Calculator 검증로직을 맡게된다. 

 

 

Plan빨간색 박스에 있는 calc.calcCallFee 를 수행하기 전 후로 검증하지 않다는 것은 

 

PlanCalculator 가 성실히 메시지를 주고 받는다는 계약은 맺은 것이다.

이것이 바로 진짜 계약이다.

 

협력을 통한 책임분할

일반적으로 precondition 은  Receiver(Plan) 이 가지고 있지만

postcondition  Receiver(Plan) 으로 부터 위임된 객체 (Calculator) 가 책임을 수행한다.

 

그러므로 우리는 precondition  postcondition  일관성있게 하자!

 

한가지 생각해보자!

Calculator 는 내부에 calls 를 가지고 있지 않고 외부로 부터 전달받는다. 

그런데 왜? 이걸 검증해야 하지? 라는 의문점이 생긴다.

 

CalculatorPlan 의 계약 관계가 바뀌었다. 

calls 가 비어있는지 아닌지는 Plan 가 하도록 계약이 변경되었다.

 

Calculator 는 더이상 0건이 아닌 calls 를 받도록 계약이 바뀌었다.

 

런타임에 의한 계약조건

위에 빨간색 박스 호출 시점이 다르다는 것을 인지하여야 한다.

즉, 두 개의 시점차이가 발생한다. 

 

Calculator check 는 잘못된 함수이다. 

Plan 에서는 setCalculator 를 호출하는 시점에 check 를 하고 있다.

근본적으로 check는 하는 이유는 Plan 이 요금을 "계산할 떄" 하기 때문에 시점 차이가 발생할 수 있다. 

 

 

그러므로 실제로 "계산할 때" check 검증 로직을 넣어주어야 한다. 

 

Plan 의 setCalculator 에서 chekc 함수를 제거한다. 

 

이제 정말 Calculator 가 "계산할 때" 검증 로직을 추가한다.

 

이제보니 Planprecondition 으로 보였지만 Calculatorinvariant 이었다.

즉, 요금을 "계산할 때" 무조건 필수 조건이 충족되어야 한다라는 의미로 invariant 로 이야기할 수 있다.