- 클라이언트가 불변식을 깨뜨리려한다는 가정을하고 방어적 프로그래밍을 해야한다.
public final class Period {
private final Date start;
private final Date end;
/**
* @param start 시작 시각
* @param end 종료 시각. 시작 시각보다 뒤여야 한다.
* @throws IllegalArgumentException 시작 시각이 종료 시각보다 늦을 때 발생한다.
* @throws NullPointerException start나 end가 null이면 발생한다.
*/
public Period(Date start, Date end) {
if (start.compareTo(end) > 0)
throw new IllegalArgumentException(
start + "가 " + end + "보다 늦다.");
this.start = start;
this.end = end;
}
public Date start() {
return start;
}
public Date end() {
return end;
}
}
Date start = new Date();
Date end = new Date();
Period p = new Period(start, end);
end.setYear(78); // p의 내부를 변경했다! 객체의 허락 없이 내부를 수정하도록 할 수 있다.
- 원인 : Date가 가변이므로 불변식이 깨진다.
- Date는 낡은 API이므로 더이상 사용 x
- 대신 LocalDateTime, ZonedDateTime, Instant 사용
public Period(Date start, Date end) {
this.start = new Date(start.getTime());
this.end = new Date(end.getTime());
if (this.start.compareTo(this.end) > 0)
throw new IllegalArgumentException(
this.start + "가 " + this.end + "보다 늦다.");
}
- 생성자에서 받은 가변 매개변수 각각을 방어적으로 복사(defensive copy)해야 한다.
- 즉 Period 안에서는 복사본을 사용한다.
- 매개변수 유효성 검사전에 방어적 복사본을 만들고, 복사본으로 유효성 검사한다.
- 멀티 스레딩 환경에서 원본 객체 유효성 검사한 후 복사본을 만드는 순간에 다른 스레드가 원본 객체를 수정할 위험이 있음
- 매개변수가 확장될 수 있는 타입(final 클래스아닌..) 이라면 방어적 복사본을 만들때 clone을 사용하면 안된다.
- clone은 하위 클래스 인스턴스를 반환할 수 있다.
- 만약 악의 가진 하위클래스가 start, end 필드 참조를 private 정적 리스트에 담아뒀다가 공격자에게 리스트에 접근하게 할 수 있다.
- clone은 하위 클래스 인스턴스를 반환할 수 있다.
start = new Date();
end = new Date();
p = new Period(start, end);
p.end().setYear(78); // p의 내부를 변경했다!
System.out.println(p);
- 원인 : 접근자 메서드가 내부의 가변정보를 직접 드러내기 때문이다.
public Date start() {
return new Date(start.getTime());
}
public Date end() {
return new Date(end.getTime());
}
- 접근자가 가변 필드의 방어적 복사본을 만들면 된다.
- 생성자와 달리 접근자 메서드는 방어적 복사에 clone 가능
- Period가 가지고 있는 Date 객체는 하위타입이 아닌게 확실하니까
-
메서드든 생성자든 클라이언트가 제공한 객체의 참조를 내부의 자료구조에 보관 할때면 항상 그 객체가 잠재적으로 변경될 수 있는지 생각
- 확신할 수 없으면 복사본 만들어 저장
-
클래스가 불변이든 가변이든, 가변인 내부 객체를 클라이언트에 반환할때는 반드시 조심.
- 안심할 수 없다면 방어적 복사
- ex) 같은 패키지 속하거나..
- 문서화 필요 (호출자가 매개변수나 반환값을 수정하지 말아야한다)
- 다른 패키지에 속한다고 항상 방어적으로 복사해 저장하는 것은 아니다.
- 매개변수로 넘기는 행위가 그 객체의 통제권을 넘긴다는 뜻이 있다. -> 클라이언트가 더이상 직접 수정하지 않아야함
- 문서화 필요
- 클래스, 클라이언트를 상호 신뢰할 수 있을때
- 불변식이 깨져도 영향이 오직 호출한 클라이언트일떄