이 내용은 엘레강트 오브젝트
를 읽으면서 정리한 내용을 포함하고 있습니다.
- 불변 클래스
- 불변객체의 특징
- 정리
불변 클래스
모든 클래스를 상태 변경이 불가능한 불변 클래스(immutable class)
로 구현하면 유지보수를 크게 향상시킨다.
불변성의 특징은 다음과 같다.
- 크기가 작음
- 응집력이 높음
- 느슨하게 결합
- 유지보수하기 쉬운 클래스
불변객체는 인스턴스를 생성한 후에 상태를 변경할 수 없는 객체이다.
class Cash {
private final int dollars;
Cash(int val) {
this.dollars = val;
}
}
- final 키워드는 생성자 외부에서 프로퍼티의 값을 수정할 경우 컴파일 에러가 발생한다.
불변 객체를 수정해야 한다면 프로퍼티를 수정하는 대신 새로운 객체를 생성해야 한다.
class Cash {
private final int dollars;
public Cash mul(int factor) {
return new Cash(this.dollar * factor);
}
}
- 불변 객체는 어떤 방식으로든 자기 자신을 수정할 수 없다.
- 항상 원하는 상태를 가지는 새로운 객체를 생성해서 반환해야 한다.
핵심은 변경 가능한 객체를 만들지 말아야 한다. 항상 불변 객체를 사용해야 한다.
불변 객체의 특징
불변 객체의 특징은 다음과 같다.
- 식별자 가변성
- 실패 원자성
- 시간적 결합
- 부수효과 제거
- NULL 참고 없애기
- 스레드 안정성
- 더 작고 더 단순한 객체
식별자 가변성
동일해 보이는 두 객체를 비교한 후 한 객체의 상태를 변경할 때, 두 객체는 더 이상 동일하지 않지만 두 객체가 동일하다고 생각하거나 반대인 경우
Map<Cash, String> map = new HashMap<>();
Cash five = new Cash("$5");
Cash ten = new Cash("$10");
map.put(five, "five");
map.put(ten, "ten");
five.mul(2);
System.out.println(map);
- 불변객체를 사용하지 않았기 때문에 Cash 객체의 five가 변경되었다.
- 불변 객체를 사용하면 객체를 map에 추가한 후에는 상태 변경이 불가능하기 떄문에 식별자 가변성 문제가 발생하지 않는다.
실패 원자성
완전하고 견고한 상태의 객체를 가지거나 아니면 실패하거나 둘 중 하나만 가능한 특성이다. 불변 객체는 내부의 어떤 것도 수정할 수 없기 때문에 이런 결함이 발생하지 않는다.
class Cash {
private int dollars;
private int cents;
public void mul(int factor) {
this.dollars *= factor;
if (/* 뭔가 잘못 됐다면 */) {
throw new RuntimeException("oops...");
}
this.cents *= factor;
}
}
- mul() 메서드를 실행하는 도중에 예외가 던져진다면 객체의 절반만 수정되고 나머지 절반은 원래 값을 유지한다.
- 이러한 에러는 매우 심각하고 발견하기 어렵다.
불변 객체는 내부의 어떤 것도 수정할 수 없기 때문에 이런 결함이 발생하지 않는다. 대신 새로운 상태를 가진 새로운 객체를 인스턴스화한다.
class Cash {
private int dollars;
private int cents;
public void mul(int factor) {
if (/* 뭔가 잘못 됐다면 */) {
throw new RuntimeException("oops...");
}
return new Cash(
this.dollars * factor,
this.cents * factor
);
}
}
시간적 결합
다음 코드는 특정한 순서로 정렬되어 있다.
Cash price = new Cash()l
// X를 계산하는 50줄의 코드
price.setDollars(x);
// Y를 계산하는 30줄 이상의 코드
price.setCents(y);
// 다른 일을 수행하는 25줄의 코드
System.out.println(price);
- setter 사이 코드가 변경될 수 있으며, 실행 순서가 변경될 수 있다.
다음과 같이 불변 객체를 이용하면 이 문제를 해결할 수 있습니다.
Cash price = new Cash(29, 95);
- 하나의 문장만으로도 객체를 인스턴스화할 수 있다.
- 인스턴스화와 초기화를 분리하지 않는다.
불변성을 활용하면 코드 전반적으로 구문 사이에 존재하는 시간적인 결합을 제거할 수 있다.
JavaBeans의 setter와 같은 Java 문법을 사용하는 절차적인 프로그래머들의 입장에서는 훌륭한 설계겠지만, 진정한 객체 사고의 관점에서는 완전히 잘못된 방식이다.
부수효과 제거
객체가 가변적일 때
- 기본적으로 누구든 손쉽게 객체를 수정할 수 있다.
클래스를 불변일 때,
- 어떤 누구도 객체를 수정할 수 없다.
- 객체의 상태가 변하지 않았다고 확신할 수 있다.
- 불변 객체를 통해 하나의 문장안에서 객체를 인스턴스화할 수 있기 때문이다.
NULL 참고 없애기
실제 값이 아닌 NULL을 참조하는 객체는 유지보수성이 저하될 수 밖에 없다.
- 언제 객체가 유효한 상태이고 언제 객체가 아닌 다른 형태로 바뀌는 지를 이해하기 어렵기 때문이다.
클래스를 어떻게 만들어야 할지 모르기에 다른 클래스가 필요하지만 새로운 클래스를 생성하는 작업이 귀찮게 여겨져서 매우 큰 클래스를 만들게 된다는 것이다.
- 사용자인 동시에 고객이고, 사원인 동시에 데이터베이스 레코드이기도 한 커다한 클래스들이 이러한 과정을 거치면서 탄생한다.
모든 객체를 불변으로 만들면 객체 안에 NULL을 포함시키는 것이 애초에 불가능해진다.
- 다시 말해서 작고, 견고하고, 응집도 높은 객체를 생성할 수 밖에 없도록 강제되기 때문에 결과적으로 유지보수하기에 훨씬 더 쉬운 객체를 만든다.
스레드 안정성
스레드 안정성이란 글자 그대로 객체가 여러 스레드에서 동시에 사용될 수 있으며 그 결과를 항상 예측가능하도록 유지할 수 있는 개체의 품질을 의미한다.
불변 객체는 실행 시점에 상태를 수정할 수 없게 금지함으로써 이 문제를 완벽하게 해결한다.
어떤 스레드도 객체의 상태를 수정할 수 없기 때문에 아무리 많은 스레드가 객체에 접근해도 문제가 없다.
가변 클래스에서 동기화 시 문제점
- 가변 클래스에 스레드 안정성을 추가하는 일은 생각처럼 쉽지 않다.
- 동기화 로직을 추가하는 일은 성능상의 비용을 초래한다.
- 각 스레드는 객체를 배타적으로 잠그기 때문에 다른 모든 스레드는 객체가 해제될 때까지 기다릴 수밖에 없다.
더 작고 더 단순한 객체
단순성은 유지보수성으로 해석할 수 있다.
- 객체가 더 단순해질 수록 응집도는 더 높아지고, 유지보수하기는 더 쉬워진다.
- 최고의 소프트웨어는 이해하고, 수정하고, 문서화하고, 지원하고, 리팩토링하기 쉽다.
클래스가 작다면 정확한 줄 수는 중요하지 않다.
- 애플리케이션에 포함되어 모든 클래스의 길이를 250줄 이하로 유지할 수 있다면, 좋은 소프트웨어 개발자이자 아키텍트라고 생각해도 무방한다.
더 작고 더 단순한 객체를 만들기 위해서는 불변 객체를 사용해야 한다.
- 불변 객체가 작은 이유는 생성자 안에서만 상태를 초기화할 수 있기 때문이다.
- 불변성은 클래스를 더 깔끔하고 더 짧게 만든다.
- 진정한 객체지향 소프트웨어는 오직 불변 객체만이 존재해야 한다.
정리
- 불변성의 특징으로는는 크기가 작으며, 응집력이 높고, 느슨하게 결합되며, 유지보수하기 쉬운 클래스이다.
- 불변성을 통해 얻고자 하는 부분은 유지보수성이며, 이해하기 쉬운 코드가 유지보수하기도 쉽다.
- 변경 가능한 객체를 만들지 말아야 하며, 항상 불변 객체를 사용해야 한다.
- 불변 객체는 내부의 어떤 것도 수정할 수 없기 때문에 이런 결함이 발생하지 않는다. 대신 새로운 상태를 가진 새로운 객체를 인스턴스화한다.
- 불변성을 활용하면 코드 전반적으로 구문 사이에 존재하는 시간적인 결합을 제거할 수 있다.
- 모든 객체를 불변으로 만들면 객체 안에 NULL을 포함시키는 것이 애초에 불가능해진다. 다시 말해서 작고, 견고하고, 응집도 높은 객체를 생성할 수 밖에 없도록 강제되기 때문에 결과적으로 유지보수하기에 훨씬 더 쉬운 객체를 만든다.
- 불변 객체는 실행 시점에 상태를 수정할 수 없게 금지함으로써 이 문제를 완벽하게 해결한다. 어떤 스레드도 객체의 상태를 수정할 수 없기 때문에 아무리 많은 스레드가 객체에 접근해도 문제가 없다.
- 불변 객체가 작은 이유는 생성자 안에서만 상태를 초기화할 수 있기 때문이다. 불변성은 클래스를 더 깔끔하고 더 짧게 만든다. 진정한 객체지향 소프트웨어는 오직 불변 객체만이 존재해야 한다.
관련해서 참고해볼 내용
'독서이야기 > 엘레강트 오브젝트 - 새로운 관점에서 바라본 객체지향' 카테고리의 다른 글
[엘레강트 오브젝트] 2.8 모의 객체(Mock) 대신 페이크 객체(Fake)를 사용하세요 (1) | 2023.03.12 |
---|---|
[엘레강트 오브젝트] 2.7 문서를 작성하는 대신 테스트를 만드세요 (0) | 2023.03.12 |
[엘레강트 오브젝트] 2.5 퍼블릭 상수(Public Constant)를 사용하지 마세요 (0) | 2023.03.12 |
[엘레강트 오브젝트] 2.4 메서드 이름을 신중하게 선택하세요 (0) | 2023.03.01 |
[엘레강트 오브젝트] 2.3 항상 인터페이스를 사용하세요 (0) | 2023.03.01 |
댓글