해당 포스팅은 <자바/스프링 개발자를 위한 실용주의 프로그래밍>의 2번째 챕터인 ‘객체의 종류’를 읽고 요약한 글이다.
자바/스프링 개발자를 위한 실용주의 프로그래밍 - 예스24
소프트웨어 개발을 잘하고 싶다면 ‘개발’ 공부를 해야 합니다!자바 개발자가 코틀린 같은 신생 언어를 다룰 수 있게 된다고 해서 개발을 더 잘하게 되는 것은 아니다. 소프트웨어 개발 능력을
m.yes24.com
책에서는 객체의 종류 중 VO, DTO, DAO, 엔티티를 주로 다룬다.
📌 VO(Value Object : 값 객체)
VO는 값 객체라는 것인데, 말 그대로 값을 가진 객체를 말한다. 객체는 클래스로 만들어지는 것이고, 클래스에는 특성과 행동을 정의할 수 있다. 즉 기본적으로 클래스는 값을 가지는 것이 아닌가라는 생각이 들었다.
정의
값 객체는 어떤 클래스로 만들어진 객체를 1, 2, 3, 4와 같이 값(value)으로 볼 수 있다는 것을 뜻한다.
🙅🏻♂️ 그렇다고 값을 가지기만 해서 모두가 값 객체인 것은 아니다.
값이라는 것이 가지는 특징을 소프트웨어 관점에서 어떻게 해석되는지를 주목해서 생각해보면 아래와 같다.
- 불변성 -> 값은 변하지 않는다. 숫자 1은 영원히 숫자 1이다.
- 동등성 -> 값의 가치는 항상 같다. 숫자 1은 100년이 지나도 숫자 1이다.
- 자가 검증 -> 값은 그 자체로 올바르다. 숫자 1이 사실은 1,000이 아닐까라는 고민을 할 필요가 없다는 의미이다.
이 3개의 특징을 전부 가진 것을 값 객체라고 하는데, 하나씩 살펴보도록 하겠다.
🔗 불변성
이미 말했듯이 값은 변할 수 없기에 클래스 내 선언된 변수(특성)들은 바뀌면 안 된다. 변수나 클래스의 변경이 불가하게 막기 위해서 final 키워드가 있다. 변수 앞에 final을 붙임으로써 수정을 막을 수 있다.
@Getter
@Setter
public class Color {
private final int r; // final 키워드는 수정이 불가하게 한다.
private final int g;
private final int b;
private final Shape shape;
public Color(int r, int g, int b, Shape shape) {
this.r = r;
this.g = g;
this.b = b;
this.shape = shape;
}
}
@Getter
@Setter
public class Shape {
private String type; // shape 클래스 내 선언된 변수가 불변하지 않다.
public Shape(String type) {
this.type = type;
}
}
이제 변수를 final로 선언했으니 불변성이 갖추어졌다고 할 수 있을까를 고민해 보면 더 확인해봐야 할 사항이 있는데, 그것은 바로 Shape 내부 상태이다. 위 예시와 같이 Color 클래스 내에 멤버 변수로 선언한 Shape가 불변한 것이 아니라면 Color 또한 불변하지 않다.
👨💻 클래스 내 모든 것이 불변해야만 한다.
변수 뿐만이 아니라 클래스 내 작성된 메서드 또한 불변성을 가져야 한다.
@Getter
@Setter
public class Color {
private final int r;
private final int g;
private final int b;
private final Shape shape;
public Color(int r, int g, int b, Shape shape) {
this.r = r;
this.g = g;
this.b = b;
this.shape = shape;
}
public Color randomColor() {
Random random = new Random();
int r = random.nextInt(256);
int g = random.nextInt(256);
int b = random.nextInt(256);
return new Color(r, g, b);
}
}
위 코드의 randomColor() 메서드는 호출할 때마다 매번 랜덤한 r, g, b값을 가지는 Color 객체를 반환할 것이다. 매번 바뀌는 것을 소프트웨어적 관점에서의 값이라고 하지 않는다고 말했기 때문에 이것은 불변성을 가진다고 할 수 없는 것이다.
👨💻 이것이 불변성이다!
위의 것들만 지켜서 불변성을 보장할 수는 없다. Color 클래스를 상속 받는 다른 클래스가 있다고 하겠다.
@Getter
@Setter
public class Color {
private final int r; // final 키워드는 수정이 불가하게 한다.
private final int g;
private final int b;
private final Shape shape;
public Color(int r, int g, int b, Shape shape) {
this.r = r;
this.g = g;
this.b = b;
this.shape = shape;
}
}
@Getter
@Setter
public class AlphaColor extends Color {
private int a;
public AlphaColor(int r, int g, int b, int a) {
super(r, g, b);
this.a = a;
}
}
Color 클래스를 상속받는 AlphaColor 내에서 변수를 불변하지 않게 선언함으로써 불변성이 깨져버렸다. 불변성을 완전히 지키기 위해서는 클래스 자체를 상속받지 못하게 만들어야 한다. 이를 위해서는 클래스를 final로 생성하는 것이다.
👨💻 불변성을 강조하는 이유가 무엇일까?
불변성을 지키기 위해 생각 이상으로 많은 것을 해야 했는데, 이토록 해서 불변성을 지켜야 하는 이유는 무엇일까?
객체지향 프로그래밍과 연관이 있는데, 객체지향 프로그래밍은 책임, 역할, 협력을 기반으로 동작하는 프로그래밍 방식이다. 객체에게 책임과 역할을 맡게 하여 객체 간의 협력을 통한 프로그래밍을 하는 것인데, 이를 위해서는 서로 간의 신뢰도가 중요하다. 즉 객체를 신뢰할 수 있게 만들기 위함이다.
👨💻 가변 객체가 가지는 문제점
불변성을 지키지 못하는 객체, 즉 가변 객체는 어떠한 문제점을 가지고 있을까? 만약 2개의 스레드에서 하나의 가변 객체를 참조한다고 가정하면 어떨지 이야기해보겠다.
public class AccountInfo {
public long id;
public int grade;
/*
생성자 생략
*/
public AccountTier getTier() {
if (grade > 100000) return "DIAMOND";
else if (grade > 50000) return "GOLD";
else if (grade > 30000) return "SILVER";
else return "BRONZE";
}
public void setTier(int grade) {
this.grade = grade;
}
}
A 스레드에서 setTier(60000); getTier(); 같은 함수들을 호출했다고 하겠다. A 스레드에서 얻는 결과는 “GOLD”가 될 것이다. 이후 B 스레드에서 setTier(45000); getTier()를 하면 B 스레드에서 “SILVER”를 얻게 될 것이다. 하지만 A 스레드에서 getTier()를 하면 “GOLD”가 아닌 “SILVER”를 얻게 될 것이다.
1개의 객체를 공유하니 이와 같은 문제가 발생했다. 이것이 불변했다면 이와 같은 일이 발생하지 않았을 것이다. 이를 해결한 방식을 예시로 살펴보겠다.
public class AccountInfo {
public final long id;
public final int grade;
public AccountTier getTier() {
if (grade > 100000) return "DIAMOND";
else if (grade > 50000) return "GOLD";
else if (grade > 30000) return "SILVER";
else return "BRONZE";
}
public AccountInfo withGrade(int grade) {
return new AccountInfo(this.id , grade);
}
}
새로운 객체를 생성하게끔 하여 2개의 스레드가 1개의 객체를 참조하는 일을 막았다.
👨💻 마무리
값 객체가 가져야 하는 특징 중 불변성에 대해서 알아보았다. 값 객체가 무엇인지만 아는 것이 아니라 왜 값 객체를 사용하는지에 대해 깊이 알 수 있었다. 객체지향 프로그래밍은 객체 간의 협력을 기반으로 동작하기에 신뢰가 중요하다. 불변성은 이러한 신뢰성을 높여주는 하나의 요소라는 생각이 들었다.
다음 포스팅에서는 값 객체가 가져야 할 특성 중 동등성과 자가 검증에 대해 이야기해 보겠다.
'IT 서적 > 자바&스프링 개발자를 위한 실용주의 프로그래밍' 카테고리의 다른 글
| CH03 - 행동 (3) | 2025.06.09 |
|---|---|
| CH 02 - 객체의 종류 | DTO, DAO, 엔티티 (1) | 2025.05.28 |
| CH 02 - 객체의 종류 | 값 객체 (2) - 동등성과 자가 검증 (3) | 2025.05.27 |
| 객체 지향적인 클래스 설계 방식 (0) | 2025.03.10 |
| CH01 - 절차 지향 프로그래밍과 객체 지향 프로그래밍 (0) | 2025.02.28 |