나만의 WAS 서버를 구현하는 도중에 클라이언트의 HTTP 메시지를 요청받고, 이를 파싱해 객체로 다루기 위한 작업이 필요했다. 이 과정에서 생성된 HttpRequestMessage 라는 클래스는 처음 파싱 이후 수정될 일이 없다는 특성을 가지고 있다. 이 특성을 만족하기 위한 객체지향 프로그래밍에서 사용되는 불변 객체에 정리하고자 한다.
불변 객체 (Immutable Object)?
불변 객체는 객체가 생성된 이후에 그 상태를 변경할 수 없는 객체를 의미한다. 한 번 생성된 객체는 재할당 되는 것이 아니라면 영원히 그 상태가 변하지 않는다.
자바에도 불변 객체가 존재한다. java.lang.String
문자열 클래스가 그 예이다.
String 으로 생성된 문자열 객체의 메서드를 직접 살펴본다면 내부의 값들이 변하지 않음을 알 수 있다. 실제로 문자열을 나누는 .split()
메서드나 .toUpperCase()
같은 경우에도 새로운 문자열을 반환할 뿐 기존의 문자열을 수정하지 않는다.
String ab = "ab";
String cd = "cd";
String abcd = ab + cd;
문자열을 합치게 되는 + 연산자를 활용한 경우에도 재할당이나 새로운 객체에 생성할 뿐 기존의 생성된 문자열 객체는 변하지 않는다.
왜 사용하는 걸까?
굳이 변하지 않도록 제한하며 사용하는 이유는 객체지향의 특성을 생각한다면 당연하다고 느낄 것이다. 느낌적인 느낌 말고 조금 더 와닿는 이유에 대해 알아보자.
예측 가능성
불변 객체는 생성된 이후 변하지 않는 특징을 가지고 있어 객체의 상태를 예측하고 추적하기 쉽다. 이는 결국 버그를 줄이고, 코드를 이해하기 쉽게 만든다. 절차지향적으로 구현된 코드는 어느 시점에 특정 객체의 상태가 변경되었는지 위에서 아래로 읽어가며 쉽게 찾을 수 있겠지만, 자바의 객체지향 코드는 쉽지 않다. 따라서 불변 객체를 활용한다면 객체의 상태를 더욱 쉽게 예측할 수 있다.
스레드의 안정성
여러 스레드가 공유 자원을 활용할 경우 동시성 문제는 치명적이다. 하지만 불변 객체의 특성을 활용한다면 write가 아닌 read-only 상태가 되어 동시성 문제에 자유롭다. 데이터 변경이 없기 때문에 동기화 문제를 해결할 필요가 없다.
재사용성
불변 객체는 생성 시점 이후 변하지 않기 때문에 객체를 생성한 뒤 캐싱하여 재사용할 수 있다. 재사용이 가능하다는 이야기는 결국 성능 향상과 이어지게 된다.
어떻게 생성하지?
public class HttpRequestMessage {
private final HttpMethod method;
private final HttpURL url;
private final String version;
private final String body;
private final Map<String, String> headers;
public HttpRequestMessage(String method, HttpURL url, String version, Map<String, String> headers, String body) {
this.method = HttpMethod.valueOf(method);
this.url = url;
this.version = version;
this.body = body;
this.headers = Collections.unmodifiableMap(headers);
}
}
다음은 내가 직접 구현한 HTTP 요청 메시지를 저장하는 클래스이다.
final 키워드를 활용하자.
불변 객체는 속성을 변경할 수 없다. 이 속성과 가장 알맞은 final 키워드를 적극적으로 활용해서 속성을 관리하자.
setter를 사용하지 말자.
모든 상태값이 final 키워드를 활용했다면 당연히 setter는 사용할 수 없을 것이다. setter는 사용해서는 안된다.
또한 상태값을 변경하는 메서드들도 존재해서는 안된다.
Collection 객체들도 불변 객체로 유지하자.
List나 Map과 같은 Collection 클래스들은 getter를 통해 받게 되면 상태를 변경할 수 있다.
이러한 과정을 막기 위해 Collections.unmodifiable
메서드를 통해 상태 값을 변경할 수 없도록 감싸줄 수 있다.
public Map<String, String> getHeaders() {
return Collections.unmodifiableMap(headers);
}
위 방법이 아니라 getter를 사용할 때 방어적 복사를 사용해 불변 객체의 특성을 만족할 수 있다.
불변 객체에 존재하는 객체도 불변해야 한다.
당연하게도 불변 객체에 존재하는 객체도 불변해야 한다.
따라서 위 예시의 HttpURL
객체 또한 불변 객체를 유지해야 한다.
스스로 공부한 내용을 토대로 정리한 글입니다. 오타나 잘못된 내용은 언제나 지적해 주세요!
참고 자료
'Study > Java' 카테고리의 다른 글
[Java] 자바의 스레드와 동작 과정 (0) | 2023.07.15 |
---|---|
[JUnit] 다양한 Assertions 사용하기 (1) | 2023.07.11 |
[JAVA] 동일성, 동등성 그리고 equals, hashcode 재정의 (0) | 2023.07.09 |
[Java] 코딩테스트에 자주 쓰는 문법 정리 (0) | 2022.09.29 |
[Java / IntelliJ] 인텔리제이 입출력 txt로 받기 (0) | 2022.09.29 |