소프트웨어의 규모가 커짐에 따라, 현실 세계의 개체(사람, 주문 등)를 컴퓨터 내부로 투영할 방법이 필요해졌고 이를 객체라 정의했습니다.
객체는 크게 두 가지 핵심 요소를 내포합니다.
첫째는 '상태'로, 대상이 가진 고유한 속성을 컴퓨터가 이해할 수 있는 데이터로 표현한 것입니다.
둘째는 '행동'으로, 상태를 제어하거나 활용하는 로직을 메서드로 정의한 것입니다.
결국 객체란 '무엇을 아는가(데이터)'와 '무엇을 하는가(메서드)'를 하나의 단위로 묶어낸 산물입니다.
"기계의 언어로 생각하지 말고,
우리가 사는 세상을 컴퓨터 속에 그대로 옮겨놓으면 어떨까?"
- 프로그래머 -좋은 객체는 “내가 책임질 수 있는 범위”가 명확합니다.
예를 들어 주문 객체는 주문의 상태 전이, 취소 가능 여부 같은 규칙을 책임지고, 외부는 “취소해줘(cancel)”처럼 요청만 하면 됩니다.
반대로 객체가 데이터만 들고 있고(= 빈 껍데기), 규칙은 서비스/컨트롤러가 전부 알고 있으면 로직이 흩어지고 유지보수가 어려워집니다.
👍 GOOD (객체가 책임을 가진 구조)
- 상태 변경은 객체 메서드로만 가능
- 불변 조건/검증이 객체 내부에 모여 있음
- 호출자는 “요청”만 하고 내부 구현을 몰라도 됨
- 도메인 규칙이 코드에서 자연스럽게 읽힘
👎 BAD (객체가 데이터만 들고 있는 구조)
- 객체는 getter/setter만 있고 로직이 없음
- 검증/규칙이 서비스에 흩어져 중복됨
- 상태 전이를 if/switch로 계속 검사
- 수정 범위가 넓어져 변경에 취약
// 주문 객체: 주문 상태를 스스로 관리(불변 조건/전이 규칙 포함)
enum OrderStatus { CREATED, PAID, SHIPPED, CANCELED }
class Order {
private OrderStatus status;
private final int totalPrice;
public Order(int totalPrice) {
if (totalPrice <= 0) throw new IllegalArgumentException("totalPrice must be > 0");
this.totalPrice = totalPrice;
this.status = OrderStatus.CREATED;
}
// 행동: 결제 처리(상태 전이 규칙)
public void pay() {
if (status != OrderStatus.CREATED) {
throw new IllegalStateException("Only CREATED order can be paid");
}
this.status = OrderStatus.PAID;
}
// 행동: 배송 처리(상태 전이 규칙)
public void ship() {
if (status != OrderStatus.PAID) {
throw new IllegalStateException("Only PAID order can be shipped");
}
this.status = OrderStatus.SHIPPED;
}
// 행동: 취소 처리(비즈니스 규칙)
public void cancel() {
if (status == OrderStatus.SHIPPED) {
throw new IllegalStateException("Shipped order can't be canceled");
}
this.status = OrderStatus.CANCELED;
}
public OrderStatus status() {
return status;
}
public int totalPrice() {
return totalPrice;
}
}
💡 TIP / 참고사항
객체 설계가 애매할 때는 “이 규칙을 누가 책임져야 하지?”를 먼저 잡으면 됩니다.
보통 상태를 가진 쪽이 규칙도 책임지는 게 자연스럽고, 그 결과 메서드(행동)가 객체 안으로 들어옵니다.
✅ 핵심 요약
- ✔️ 객체는 상태(데이터) + 행동(메서드)을 가진 책임 단위다.
- ✔️ 좋은 객체는 규칙(불변 조건)을 스스로 지키도록 메서드로 상태 변경을 통제한다.
- ✔️ 객체가 데이터만 들고 있다면 로직이 흩어지고 유지보수가 급격히 어려워진다.