'객체지향의 사실과 오해' 스터디 2주차에서 객체를 정의하는 기준에 대한 이야기를 주고받았다.
Q : 객체를 정의하는 기준은 무엇일까요?
A : 상태?
객체는 상태와 행위를 관리하므로 상태가 없다면 객체라고 정의하기 모호함
-> 객체는 자율성이 중요하기 때문에 상태가 정의되어있지 않다면 행위 또한 고정되므로 자율성이 있다고 말하기 어려움.
따라서 유틸리티 객체, 인스턴스 변수가 없는 객체들은 객체라고 말하기 어려우며 객체보다는 메서드의 ‘묶음’에 가깝다.
👏👏👏
나의 누추한 질문에 정말 현명한 대답을 들으며 속으로 감사를 느꼈다.
동시에 내가 알고 있는 점과 반박되는건지, 또는 모르는 부분이 있어서 저분처럼 이해가 와닿지 않는건지 궁금했지만 그건 직감일뿐, 정확하게 공유하고 나눌 수 있는 이유가 되지 않아서 저 답변에 만족했다.
내가 궁금했던 점은 다음과 같다.
- 상태를 가진다는 것은 동시에 변경 가능성을 가지는 것 아닌가?
- 변경 가능성이 줄어드는게 의존성 관리에 더 수월한 구조 아닌가?
- 그렇다면 상태가 적거나, 또는 아예 없는 객체라면 더 좋은 것 아닌가?
상태가 없으면 객체로써 인정받을 수 없는건가..?
조영호 님의 의견을 들어보자.
객체지향의 사실과 오해 2장에 '왜 상태가 필요한가'(p47)를 간단히 정리하자면
상태란
- 특정 시점에 객체가 가지고 있는 정보의 집합
- 예시
'앨리스'(이상한 나라의 앨리스에 나오는) 객체의 키,
행동에 따라 '앨리스' 객체는 커지거나 작아지는 모습으로 변경된다.
상태라는 개념을 통해 객체의 행동 방식을 단순하게 이해할 수 있다는 의미이다.
상태가 없으면 객체의 행동 방식을 다 설명하기 너무 어려움..
객체지향 프레임워크인 스프링에서 싱글톤 설계 시 주의점으로 무상태를 지향해야 한다는 내용이 있다.
스프링 컨테이너 특성 상 멤버변수를 두게 되는 경우 해당 멤버변수에 들어간 값이 다른 곳에서 상태를 유지(Stateful)한 채 재사용된다는 문제점 때문에
서로 다른 요청임에도 불구하고 값이 꼬여버리는 경우가 생기고,
필드 값이 없는 무상태(Stateless)로 메서드만 공유하는 경우라는 내용이다.
상품을 주문하는 객체를 통해 예시가 있다.
다음 Service 객체에 필드가 존재한다면
public class Service {
private int price; // stateful, 상태 유지
public void order(String name, int price) {
this.price = price;
}
public int getPrice() {
return price;
}
}
statefulService1 객체의 price 값이 20000으로 변경된 것을 이유로 테스트에 실패하게 된다.
@Test
void serviceSingleton() {
// given
ApplicationContext ac = new AnnotationConfigApplicationContext(TestConfig.class);
Service statefulService1 = ac.getBean("statefulService", Service.class);
Service statefulService2 = ac.getBean("statefulService", Service.class);
//when
statefulService1.order("userA", 10000);
statefulService2.order("userB", 20000);
int expect = statefulService1.getPrice();
int actual = 10000;
// then
assertEquals(expect, actual);
}
static class TestConfig() {
@Bean
public StatefulService statefulService(){
return new Service();
}
}
statefulService1과 statefulService2가 서로 다른 요청, 스레드 별로 각자의 스택 메모리 영역을 차지하더라도 스프링 컨테이너 동작으로 인해 이미 힙 영역에 bean 객체가 로딩되어 있기 때문에 bean의 상태가 유지되는 구조로 설계할 경우 문제가 발생한다고 한다.
이러한 문제를 해결하기 위해 무상태 객체를 도입한다.
public class Service {
// 상태를 유지하는 필드 제거
public void order(String name, int price) {
this.price = price;
}
public int getPrice() {
return price;
}
}
필드를 없앤 무상태 객체로 변경했다는 것은 객체의 상태를 저장해서 유지하는 것이 아닌, 값이 들어오면 바로 반환하는 구조로 변경되었다는 의미이다.
이것은 주문이 공유되었던 기존 문제를 해결한다.
즉, 주문(Service) 객체는 싱글톤으로 동일하게 사용하되 각 입력 금액에 맞게 주문금액이 정상적으로 설정된다.
```java
public class Service {
// stateless
public void order(String name, int price) {
this.price = price;
}
public int getPrice() {
return price;
}
}
@Test
void serviceSingleton() {
// given
ApplicationContext ac = new AnnotationConfigApplicationContext(TestConfig.class);
StatefulService statefulService1 = ac.getBean("statefulService", Service.class);
StatefulService statefulService1 = ac.getBean("statefulService", Service.class);
//when
statefulService1.order("userA", 10000);
statefulService2.order("userB", 20000);
int expect = statefulService1.getPrice();
int actual = 10000;
// then
assertEquals(expect, actual);
}
static class TestConfig() {
@Bean
public StatefulService statefulService(){
return new Service();
}
}
이처럼 상황에 따라 객체의 상태는 포함시키지 않는 경우도 있다.
상태가 없어도.. 객체가 되네?
- stateless object라는 개념이 여기에 설명되어 있습니다.
원점으로 돌아와서 책의 내용에 따르면
객체에는 상태가 필요하다.
객체의 값 변화를 과거부터 모두 나열하지 않고,
단순하게 '상태'라는 개념으로 현재를 식별하기 수월하도록 만들어주기 때문이다.
하지만, '상태가 없으면 객체가 아니다.' 라는 내용은 못 찾은 것 같다.
어쩌면 그 이유가 책에 스며들어있는데도 불구하고 경험이 부족한 내가 발견 못하고 있는지도 모르겠다.
그래서 스터디원이 좋은 답변을 해줬음에도 불구하고, 굳이 하찮은 나의 생각과 비교해보고 어떤게 정확한 사실일지 찾아도 보았지만 아직 명확한 정답은 찾지 못 했다.
객체지향을 왜 사용하는지 고민했던 이후로 어떻게 사용할지를 고민하게 되었고,
이라고 생각하는 중이다. 어떤게 정답일진 아직 모르겠다.
아마 상황에 따라 필요한 정답은 달라지기 때문이지 않을까라고 조심스럽게 유추할 수도 있을 것 같다.
혹시 하찮은 이 글을 읽고 누군가 자신의 생각을 자유롭게 말해 주신다면 정말 감사하겠습니다.
- 상태가 없으면 객체가 아닌게 맞을까요?
- 그게 아니라면 객체인 것과 객체가 아닌 것을 구분하는 기준은 무엇일까요?
- 객체는 어떻게 정의해야 할까요?