본문 바로가기

Study/오프젝트

부록 A) 계약에 의한 설계

1. 협력과 계약

계약은 협력을 명확하게 정의하고 커뮤니케이션할 수 있는 범용적인 아이디어다. 각 계약 당사자는 계약으로부터 이익을 기대하고 이익을 얻기 위해 의무를 이행한다. 여기서 중요한 부분은 한쪽의 의무가 반대쪽의 권리가 되는 것이다.

 

예를 들어 이사를 하기 위해 '이삿짐센터'에 작업을 위탁하고 계약을 체결할 것이다. 여기서 '나'의 입장에서 의무는 '이사짐센터'에 이사비용을 지불하는 것이다. 반대로 '이삿짐센터'의 입장에서 의무는 '나'의 이삿짐을 옮겨주는 것이다. 둘 중 계약서에 명시된 내용을 위반한다면 계약은 정상적으로 완료되지 않는다.

 

2. 계약에 의한 설계

아래의 예를 들어보자. 음식을 구매를 한다고 가정해 보자. 음식 클래스를 보면 name, calory라는 속성을 가진다. 그런데 만약 음식을 구매를 하려고 한다고 치면 음식이름이 null이면 안되고 calory는 음수가 아니 여야 한다. 이런 조건을 만족하지 않는다면 음식을 구매하는 메서드를 호출할 수 없어야 한다. 

public class Food {
	private String name;
	private int calory;
}

 

계약에 의한 설계를 구성하는 세 가지 요소(사전조건, 사후조건, 불변식)에 대해 알아보자.

 

2.1 사전조건

메서드가 정상적으로 실행되기 위해 만족해야 하는 조건이다. 만약 음식을 구매를 하려고 할 때 음식의 이름이 null이 아니어야 하고 칼로리는 음수가 아닌 0 이상의 숫자여야 한다.

 

2.2 사후조건

메서드의 실행 결과가 올바른지를 검사하고 실행 후에 객체가 유효한 상태로 남아 있는지를 검증한다. 일반적으로 사후조건은 세 가지 용도로 사용된다.

  • 인스턴스 변수의 상태가 올바른지를 서술하기 위해
  • 메서드에 전달된 파라미터의 값이 올바르게 변경됐는지를 서술하기 위해
  • 반환값이 올바른지를 서술하기 위해

하지만 사전조건보다 사후조건을 정의하는 것이 어려운 경우가 있다. 한 메서드 안에서 return 문이 여러 번 나올 경우, 실행 전과 실행 후의 값을 비교해야 하는 경우. 자바언어에서는 이를 지원하는 라이브러리를 제공하지 않기 때문에 코드보다는 의미를 파악하자.

 

2.3 불변식

인스턴스 생명주기 전반에 걸쳐 지켜야 하는 규칙을 명세한다. 일반적으로 불변식은 객체의 내부 상태와 관련이 있다.

  • 불변식은 클래스의 모든 인스턴스가 생성된 후에 만족돼야 한다.
  • 불변식은 클라이언트에 의해 호출 가능한 모든 메서드에 의해 준수돼야 한다.

 

3. 계약에 의한 설계와 서브타이핑

계약에 의한 설계는 클라이언트가 만족시켜야 하는 사전조건과 클라이언트의 관점에서 서버가 만족시켜야 하는 사후조건을 기술한다. 즉 서브타입이 LSP를 만족시키기 위해서는 클라이언트와 슈퍼타입 간에 체결된 계약을 준수해야 한다. LSP는 두 가지 종류로 세분화할 수 있다. 첫 번째 규칙은 협력에 참여하는 객체에 대한 기대를 표현하는 계약 규칙이고, 두 번째 규칙은 교체 가능한 타입과 관련된 가변성 규칙이다.

 

계약 규칙

  • 서브타입에 더 강력한 사전조건을 정의할 수 없다.
    • 나눗셈을 담당하는 슈퍼타입이 있다고 가정해 보자. 여기서 divide(int number) 메서드의 사전조건은 'number가 0이 아니어야 한다'라고 해보자. 서브타입에서 메서드를 오버라이딩해서 사용할 경우 사전조건을 'number가 0이 아니어야 하고, 정수여야 한다'라고 사전조건을 강화하면 문제가 생긴다. 슈퍼타입을 참조변수로 서브타입 객체를 참조하고 divide(-2)를 호출하면 슈퍼타입에서는 허용되던 값이 서브타입에서는 사전조건을 위반하게 된다. 이로서 LSP가 깨지게 된다. 그와 반대로 사전조건을 완화하는 건 가능하다.
  • 서브타입에 더 완화된 사후조건을 정의할 수 없다.
    • 나이를 반환하는 슈퍼타입이 있다고 가정해 보자. 슈퍼타입의 getAge() 메서드의 사후조건은 '0 이상의 정수를 반환한다'라고 하자. 서브타입에서 getAge()의 사후조건을 '0이상의 정수를 반환하거나 -10 이상의 정수를 반환한다.'라는 사후조건을 완화를 한다고 해보자. 그럼 슈퍼타입을 참조변수로 서브타입 객체를 참조하고 getAge() 메서드를 호출하면 원하는 값을 받지 못할 수 도 있다.
  • 슈퍼타입의 불변식은 서브타입에서도 반드시 유지돼야 한다.
    • 은행계좌를 생각해 보자. 슈퍼타입의 불변식은 '계좌는 음수가 될 수 없다.'라고 해보자. 만약 서브타입에서 계좌가 0 미만이 될 수 있도록 허용을 하게 되면 슈퍼타입의 불변식은 깨지게 된다.

가변성 규칙

처음 보는 단어가 많아 GPT에 간단히 검색을 해봤다.

  • 서브타입의 메서드 파라미터는 반공변성을 가져야 한다.
    • 반공변성 : 하위 타입 관계가 제네릭 타입에 대해 반대로 유지될 때
  • 서브타입의 리턴 타입은 공변성을 가져야 한다.
    • 공변성 : 하위 타입 관계가 제네릭 타입에 대해서도 유지될 때
  • 서브타입은 슈퍼타입이 발생시키는 예외와 다른 타입의 예외를 발생시켜서는 안 된다.
    • 무공변성 : 하위 타입 관계가 제네릭 타입에 대해 전혀 유지되지 않을 때

'Study > 오프젝트' 카테고리의 다른 글

14) 일관성 있는 협력  (0) 2024.02.04
12) 다형성  (1) 2024.01.25
11) 합성과 유연한 설계  (0) 2024.01.17
10) 상속과 코드 재사용  (0) 2024.01.10
09) 유연한 설계  (1) 2024.01.06