참조를 값으로 변경하는 리팩터링 기법이다.
참조를 값으로 변경하는 것이니까 이는 mutable 한 값을 immutable 하게 변경하겠다는 의미를 내포한다.
객체를 크게 두 가지 객체로 볼 수 있다.
참조 객체는 얼마든지 그 내부의 값이 변경될 수 있는 객체를 말한다.
값 객체는 객체의 필드의 값들을 보고 동등성을 판단하는 보통 불변 객체라고 하는 객체이다.
보통 사용 기준을 나눌 때 객체의 변경사항을 다른 코드에도 전파시키고 싶다면 보통 참조 객체를 사용하고
반면 값 객체인 경우 그러한 사이드 이펙트 없이 사용하고 싶다면 값 객체를 사용하게 할 수 있다.
다음 코드는 값이 변경될 수 있는 참조 객체를 의미하는 코드이다.
public class TelephoneNumber {
private String areaCode;
private String number;
public String areaCode() {
return areaCode;
}
public void areaCode(String areaCode) {
this.areaCode = areaCode;
}
public String number() {
return number;
}
public void number(String number) {
this.number = number;
}
}
해당 필드의 값을 setter를 통해 값을 변경할 수 있다.
이러한 객체를 참조로 가지고 있는 또 다른 클래스를 확인해보자.
public class Person {
private TelephoneNumber officeTelephoneNumber;
public String officeAreaCode() {
return this.officeTelephoneNumber.areaCode();
}
public void officeAreaCode(String areaCode) {
this.officeTelephoneNumber.areaCode(areaCode);
}
public String officeNumber() {
return this.officeTelephoneNumber.number();
}
public void officeNumber(String number) {
this.officeTelephoneNumber.number(number);
}
}
Person 클래스는 TelephoneNumber를 참조하고 있다.
그리고 메서드를 통해서 래퍼런스 객체의 값을 변경하고 있습니다.
만약 TelephoneNumber를 VO로 변경하고 싶다면 다음과 같이 리팩터링 할 수 있습니다.
public class TelephoneNumber {
private final String areaCode;
private final String number;
public TelephoneNumber(String areaCode, String number) {
this.areaCode = areaCode;
this.number = number;
}
public String areaCode() {
return areaCode;
}
public String number() {
return number;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
TelephoneNumber that = (TelephoneNumber) o;
return Objects.equals(areaCode, that.areaCode) && Objects.equals(number, that.number);
}
@Override
public int hashCode() {
return Objects.hash(areaCode, number);
}
}
이 경우 반드시 equals와 hashCode를 생성해주어야 합니다.
즉 VO의 데이터를 기준으로 판단할 수 있도록 해야 합니다. 동등성이라고도 합니다.
실제 클라이언트에서는 이제 새로운 VO 객체를 만들도록 변경해주어야 합니다.
public class Person {
private TelephoneNumber officeTelephoneNumber;
public String officeAreaCode() {
return this.officeTelephoneNumber.areaCode();
}
public void officeAreaCode(String areaCode) {
this.officeTelephoneNumber = new TelephoneNumber(areaCode, this.officeNumber());
}
public String officeNumber() {
return this.officeTelephoneNumber.number();
}
public void officeNumber(String number) {
this.officeTelephoneNumber = new TelephoneNumber(this.officeAreaCode(), number);
}
}
그리고 사실 자바 14 버전 이상으로는 record로 간단하게 표현할 수 있다.
public record TelephoneNumber(String areaCode, String number) {
}
@Test
public void testEquals() throws Exception {
TelephoneNumber telephoneNumber = new TelephoneNumber("123", "1234");
TelephoneNumber telephoneNumber1 = new TelephoneNumber("123", "1234");
Assertions.assertEquals(telephoneNumber1, telephoneNumber);
}
마지막으로 불변 객체를 사용하는 의도는 다양할 수 있지만, 가장 흔한 이유는 안정성과 보안을 위해서입니다.
불변 객체는 생성된 후에는 값이 변하지 않는 객체를 말하며, 이는 코드의 오류를 줄이고 애플리케이션의 안정성을 높일 수 있습니다.
불변 객체는 일반적으로 여러 스레드가 동시에 접근해도 안전하게 사용할 수 있으며, 이는 병렬 애플리케이션을 개발할 때 유용합니다.
불변 객체는 일반적으로 캐시 하거나 공유하기에도 적합합니다.
추가적으로 JPA 엔티티 내부 @Embedded을 사용하는 필드도 값 객체로 사용하는 것이 좋습니다.
실제 참조 객체로 사용하다보면 의도치 않게 여러 엔티티에 공유되어 원치 않는 값 변경에 버그를 발생할 수 있기 때문인데요.
이를 불변객체로 만들지 않고 코드 레벨에서 강제로 막을 수 있는 부분은 없다고 봐도 무방할 것 같습니다.
'독서에서 한걸음' 카테고리의 다른 글
Shotgun Surgery (0) | 2022.12.18 |
---|---|
Divergent Change (0) | 2022.12.14 |
Replace Derived Variable with Query, Combine Functions into Transform (0) | 2022.12.11 |
Separate Query from Modifier && Remove Setting Method (0) | 2022.12.10 |
Split Variable (0) | 2022.12.05 |
댓글