값의 특징과 VO

옹심이·2024년 12월 24일
0
post-thumbnail

시작하며

VO는 값 객체를 의미하며 쓰기 작업이 불가한 읽기 전용 객체이다. VO는 존재만 알고 있었지 한 번도 써본적이 없다.

public final class Color {
    public final int r;
    public final int g;
    public final int b;
    
    public Color(int r, int g, int b){
        if(r<0 || r>255 ||
           g<0 || g>255 ||
           b<0 || b>255){
            throw new IllegalAccessException("RGB should be 0 to 255")
        }
        
        ...
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) {
            return true;
        }
        if (o == null || getClass() != o.getClass()) {
            return false;
        }
        Color color = (Color) o;
        return r == color.r && g == color.g && b == color.b;
    }

    @Override
    public int hashCode() {
        return Objects.hash(r, g, b);
    }
}

짜잔, VO의 예시 코드이다. 일반적인 객체와의 차이가 무엇일까? 그리고 무슨 의미를 가지는 객체일까?

VO인 Color 클래스는 만들어진 객체를 1, 2, 3 같은 값과 같이 본다는 의미이다. Color는 객체이며 동시에 값이다. 그래서 값 객체라고 부른다.

이제 값이 소프트웨어 개발 관점에서 어떤 해석 의미를 가지느냐가 관건이다. 이 특징을 파악하며 VO는 어떤 목적을 가지는지 알아보도록 하자

값의 특징과 VO

  • 불변성 : 변하지 않는 속성이다. 예를 들면 숫자 1은 앞으로도 쭉 숫자 1이다.
  • 동등성 : 값의 가치는 항상 같다. 숫자 1이 어디에 언제 있던 숫자 1은 그냥 1이다.
  • 자가 검증 : 값은 그 자체로 올바르다. 1이 2일까? 고민할 필요가 없다는 의미이다.

특징 1 - 불변성

숫자 1은 100년전이나 지금이나 그냥 1일 뿐이다. 그래서 값 객체를 의미하는 VO는 불변성을 가진다.

변하지 않는다는 개념은 시스템의 복잡도 측면에서 소프트웨어 설계에서 아주 중요하다. 이러한 특성 때문에 소프트웨어 중 일부를 예측하고 신뢰할 수 있기 때문이다.

예를 들면, 웹 서버를 만들 때 다른 서버의 API 호출 또는 데이터베이스 접근 동작에서 네트워크 호출은 불확실하다. 코드와 관계 없이 네트워크 환경에 따라 프로그램이 마음대로 동작하지 않을 수 있다.

이처럼 소프트웨어는 불확실한 요소가 너무 많기 때문에 확실한 신뢰를 가질 수 있는 영역이 최대한 많은 것이 중요하며 이러한 관점에서 불변성을 가지는 것은 아주 중요하다.

public final class Color {
    public final int r;
    public final int g;
    public final int b;
    ...
}

다시 코드를 보면 모든 멤버 변수가 final로 선언 되어 불변성을 띄고 있는 것을 확인할 수 있다.

하지만 멤버 변수가 모두 final로 선언되어 있다고 해도 원시 타입이 아니라 참조 타입인 객체가 있으면 불변성이 보장되지 않기 때문에 VO가 아니다.

불변성이 깨지는 예시 1 - 가변 객체를 참조하는 final 변수

public final class FillColor{
    ...
	  public final Shape shape;
	  ...
}

@Data
public class Shape {
    private int width;
    private int height;
}

이 코드를 보면 FilledColor는 지정된 Shape에 들어가는 색상을 의미하는 클래스지만, Shape에는 불변성이 없는 멤버 변수를 가지고 있다.

따라서, 참조 객체의 불변성이 지켜지지 않으면 객체의 멤버 변수를 final로 지정해도 불변성이 깨진다.

불변성이 깨지는 예시 2 - 상속

class AlphaColor extends Color{
	public int a;
	
	public AlphaColor(int r, int g, int b, int a){
		...
	}
}

Color 클래스에서 파생된 AlphaClass의 멤버 변수는 불변성이 없다. 이처럼 상속에 의해서도 불변성이 깨질 수 있으니 초기 설계에 신경써야한다.

불변성을 보장하는 잘된 설계 예시

@AllArgsConstructor
class AccountInfo{
	public final long mileage;

	public AccountInfo getLevel(long mileage) {
		if(mileage>100_000)return AccountLevel.DIAMOND;
		else if(mileage > 50_000)return AccountLevel.GOLD;
		else if(mileage > 10_000)return AccountLevel.BRONZE;
	}

	public AccountInfo withMileage(long mileage){
		return new AccountInfo(this.id, mileage);
	}
}

이 코드에서는 스레드 A와 스레드 B가 각각 레벨을 불러오는 메서드를 호출했을 때 어떻게 불변성을 유지할 수 있는지 보여준다.

멤버 변수를 final로 선언하였고 각 스레드마다 새로운 AccountIno 객체를 사용하기 때문에 같은 객체에 같은 메서드를 호출하면 항상 같은 결과를 반환받을 수 있다.

불변성의 중요성

불변 객체를 사용하면 프로그램의 신뢰성이 높아진다. 만약 위 코드에서 불변성을 보장하지 않는다면 객체에 작업을 요청할 때마다 예측할 수 없는 값을 받게 되어 혼란스러울 것이다.

또한 객체의 상태를 확인하기 위해 불필요한 검증 로직을 추가해야 할 수 있다.

불변성은 작은 실수로도 쉽게 깨질 수 있다. 소프트웨어는 상태 변화에 따라 다르게 동작하기 때문에 불확실성을 완전히 제거하는 것은 불가능하다. 하지만 이러한 불확실성을 최소화하는 것이 우리의 과제일 것이다.

특징 2 - 동등성

Color green1 = new Color(0);
Color green2 = new Color(0);

green1==green2는 true와 false 중 무엇을 반환할까?

초록색이라는 같은 값을 가지고 있으니 같다고 볼 수도 있고, 서로 다른 객체를 참조하고 있으니 다르다고 볼 수도 있다.

VO는 이 문제에 대해 내재된 의미인 동등성에 중점을 두고 판단한다.

자바에서는 기본적으로 equals와 hashCode 메서드를 통해 메모리 주소 값으로 비교한다. 이는 VO의 설계 의도와 맞지 않으므로, 두 메서드를 오버라이딩하여 객체의 상태를 비교하도록 수정해야 한다.

이러한 메서드 오버라이딩이 번거롭게 느껴진다면, 롬복의 @Value 어노테이션을 활용할 수 있다.

  • @Value는 객체의 상태를 비교하는 equals와 hashCode 메서드를 자동으로 생성한다.
  • @Value는 모든 멤버 변수를 final로 선언한다.
  • 클래스를 final로 선언한다.
  • 클래스가 VO임을 명시적으로 표현한다.

특징 3 - 자가 검증

자가 검증은 스스로 상태가 유효한지 검증할 수 있는 능력이다. VO의 목표는 신뢰와 예측이 가능한 객체를 만드는 것이다. 불변성과 동등성을 보장해도 값 자체가 잘못되었다면 이 객체는 신뢰할 수 없다.

하지만 자가 검증이 가능하다면 상태 검증을 위한 추가 로직이 필요없다. 따라서 VO에는 자가 검증 코드가 있어야한다.

마치며

솔직히 한번도 사용해본적이 없기 때문에 가장 이해하기 어려웠던 객체이다. 하지만 값의 특징을 알아보며 VO의 근간이 무었있지 파악할 수 있었다.
앞으로 VO를 사용할 일이 있으면 VO의 목적을 고민해 보고 신뢰 가능한 객체를 만드는 것이 좋을 것 같다.

0개의 댓글