본문 바로가기
독서이야기/엘레강트 오브젝트 - 새로운 관점에서 바라본 객체지향

[엘레강트 오브젝트] 2.6 불변 객체로 만드세요

by 사랑꾼이야 2023. 3. 12.
반응형

이 내용은 엘레강트 오브젝트 를 읽으면서 정리한 내용을 포함하고 있습니다.

  • 불변 클래스
  • 불변객체의 특징
  • 정리

불변 클래스

모든 클래스를 상태 변경이 불가능한 불변 클래스(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을 포함시키는 것이 애초에 불가능해진다. 다시 말해서 작고, 견고하고, 응집도 높은 객체를 생성할 수 밖에 없도록 강제되기 때문에 결과적으로 유지보수하기에 훨씬 더 쉬운 객체를 만든다.
  • 불변 객체는 실행 시점에 상태를 수정할 수 없게 금지함으로써 이 문제를 완벽하게 해결한다. 어떤 스레드도 객체의 상태를 수정할 수 없기 때문에 아무리 많은 스레드가 객체에 접근해도 문제가 없다.
  • 불변 객체가 작은 이유는 생성자 안에서만 상태를 초기화할 수 있기 때문이다. 불변성은 클래스를 더 깔끔하고 더 짧게 만든다. 진정한 객체지향 소프트웨어는 오직 불변 객체만이 존재해야 한다.

관련해서 참고해볼 내용

반응형

댓글