클래스와 객체의 책임
클래스는 관련된 데이터와 동작을 하나의 타입으로 정의하고 객체는 그 설계로 만들어진 개별 상태입니다.
단순히 필드와 메서드를 한 파일에 모았다고 좋은 객체가 되는 것은 아닙니다.
객체가 스스로 지켜야 하는 규칙과 외부에 제공할 동작을 함께 정해야 합니다.
생성된 두 객체는 같은 클래스를 사용해도 서로 다른 필드 값을 가질 수 있습니다.
이 절에서는 데이터 소유자와 동작 책임자를 같은 경계에 두는 기준을 살펴봅니다.
클래스는 가능한 상태와 동작을 정의한다
필드는 객체가 기억할 상태이고 메서드는 그 상태를 읽거나 바꾸는 규칙입니다.
외부 코드가 필드를 직접 조합해야 한다면 책임이 밖으로 새어 나갑니다.
객체마다 인스턴스 상태가 따로 있다
new를 두 번 호출하면 같은 클래스의 서로 다른 객체가 만들어집니다.
한 객체의 필드를 바꿔도 다른 객체에는 영향을 주지 않습니다.
같은 클래스로 객체 두 개를 만들고 한쪽 잔액만 바꿔 봅니다. 클래스는 가능한 상태와 동작을 한 번 정의하지만, 인스턴스 필드는 생성된 객체마다 따로 존재한다는 사실이 출력에 드러납니다.
public class AccountObjects {
static final class Account {
private final String owner;
private int balance;
Account(String owner) { this.owner = owner; }
void deposit(int amount) {
if (amount <= 0) throw new IllegalArgumentException("amount");
balance += amount;
}
String summary() { return owner + ":" + balance; }
}
public static void main(String[] args) {
Account ada = new Account("Ada");
Account linus = new Account("Linus");
ada.deposit(100);
linus.deposit(40);
System.out.println(ada.summary());
System.out.println(linus.summary());
}
}첫 번째 객체의 입금이 두 번째 객체에 영향을 주지 않아야 정상입니다. 둘이 함께 바뀐다면 인스턴스 상태여야 할 필드를 static으로 두었거나 같은 가변 객체를 공유하는지 확인합니다.
Ada:100
Linus:40같은 Account 클래스로 만든 두 객체가 각자의 balance를 독립적으로 보관합니다.
참조 변수와 객체를 구분한다
변수에는 객체 위치를 나타내는 참조가 저장됩니다.
두 변수가 같은 객체를 가리킬 수 있고 변수 하나가 null일 수도 있습니다.
참조 변수에는 객체 자체가 아니라 객체를 찾는 값이 들어갑니다. 두 참조가 같은 객체를 가리킬 수도 있고 참조가 null일 수도 있으므로, 변수 선언과 객체 생성은 서로 다른 사건입니다.
Account first = new Account("A");
Account alias = first;
alias.deposit(10); // 두 변수가 같은 객체를 가리킴
Account absent = null;
// absent.deposit(10); // 실행 시 NullPointerException
// first.balance = -100; // 컴파일 오류: private 필드행동 중심으로 공개 API를 고른다
잔액 필드를 공개하기보다 deposit, withdraw처럼 유효성 규칙을 포함한 동작을 제공합니다.
객체가 자신의 불변식을 한곳에서 지킬 수 있습니다.
필드를 그대로 공개하면 호출자가 검증 없이 상태를 만들 수 있습니다. deposit, withdraw처럼 의도가 있는 동작을 열어 두면 음수 금액 거부나 잔액 부족 규칙을 객체 한곳에서 지킬 수 있습니다.
클래스를 설계할 때는 어떤 데이터를 갖는가보다 그 데이터의 유효성을 누가 책임지는가를 먼저 정해야 합니다.