불변 객체

Glen·2023년 4월 2일
0

배운것

목록 보기
5/37

불변 객체가 뭘까?

불변 객체는 생성된 후 변경할 수 없는 객체를 말한다.

불변 객체는 생성되면 그 상태를 수정할 수 없고, 수정하는 모든 시도는 업데이트된 상태를 가진 새로운 객체가 생성된다.

불변 객체를 만드려면 다음과 같은 규칙을 따르면 된다.
1. 모든 필드를 private 및 final로 선언하여 생성자 외부에서 수정할 수 없도록 한다.
2. 필드에 대한 setter를 제공하지 않는다.
3. 클래스에서 사용되는 모든 가변 객체를 안전하게 캡슐화되도록 한다.

해당 규칙을 따르며 불변 객체를 만들어보자.


다음 코드와 같이 Money라는 클래스가 있다.

public class Money {  
    private int amount;  
  
    public Money(int amount) {  
        this.amount = amount;  
    }  
  
    public int getAmount() {  
        return amount;  
    }  
  
    public void setAmount(int amount) {  
        this.amount = amount;  
    }  
}

1번 규칙을 따라 필드를 final 로 선언하면 자연스럽게 setter가 사라진다.

public class Money {  
    private final int amount;  
  
    public Money(int amount) {  
        this.amount = amount;  
    }  
  
    public int getAmount() {  
        return amount;  
    }  
}

하지만 3번 규칙은 뭔가 말이 쉽게 와닿지가 않는다.
모든 가변 객체를 캡슐화 한다는 것이 뭘까?

여기 Person이라는 클래스를 List로 관리하는 People이라는 클래스가 있다.

public class Person {  
    private String name;  
  
    public Person(String name) {  
        this.name = name;  
    }  
  
    public String getName() {  
        return name;  
    }  
  
    public void setName(String name) {  
        this.name = name;  
    }  
}
public class People {  
    private final List<Person> people;  
  
    public People(List<Person> people) {  
        this.people = people;  
    }  
  
    public List<Person> getPeople() {  
        return people;  
    }  
}

Person은 불변 객체가 아니지만, People은 1번, 2번 규칙을 따랐으니 불변 객체 라고 할 수 있을까?

아니다. 불변 객체라고 할 수 없다.

List<Person> peopleList = new ArrayList<>();  
peopleList.add(new Person("1"));  
peopleList.add(new Person("2"));  
peopleList.add(new Person("3"));  
  
People people = new People(peopleList);  
List<Person> foundPeople = people.getPeople();  
foundPeople.add(new Person("4"));

다음과 같이 List 필드를 그대로 가져온다면 final로 선언을 하여도 수정이 가능하다.
즉, setter를 사용하는 것과 같다.

따라서 다음과 같이 List.copyOf 혹은 Collections.unmodifiableList 메서드를 사용하여 컬렉션의 값을 변경할 수 없도록 해야한다.

public class People {  
    private final List<Person> people;  
  
    public People(List<Person> people) {  
        this.people = people;  
    }  
  
    public List<Person> getPeople() {  
        // return List.copyOf(people);  
        return Collections.unmodifiableList(people);  
    }  
}
List<Person> peopleList = new ArrayList<>();  
peopleList.add(new Person("1"));  
peopleList.add(new Person("2"));  
peopleList.add(new Person("3"));  
  
People people = new People(peopleList);  
List<Person> foundPeople = people.getPeople();
foundPeople.add(new Person("4")); // Exception 발생!

이제 정말 끝일까?
아니다. 아직도 할 일이 남았다.
Person 클래스는 불변 객체가 아니다.
따라서 People에서 읽기 전용인 컬렉션을 반환한다고 해도, 컬렉션이 가지고 있는 객체가 변경되면 상태가 변경된다.

List<Person> peopleList = new ArrayList<>();  
peopleList.add(new Person("1"));  
peopleList.add(new Person("2"));  
peopleList.add(new Person("3"));  
  
People people = new People(peopleList);  
List<Person> foundPeople = people.getPeople();  
Person person = foundPeople.get(0);  
person.setName("4");  
  
System.out.println(people.getPeople()); // [4, 2, 3]

따라서 Person도 불변 객체로 만들어줘야 People불변 객체라고 할 수 있다.

public class Person {  
    private final String name;  
  
    public Person(String name) {  
        this.name = name;  
    }  
  
    public String getName() {  
        return name;  
    }  
}
List<Person> peopleList = new ArrayList<>();  
peopleList.add(new Person("1"));  
peopleList.add(new Person("2"));  
peopleList.add(new Person("3"));  
  
People people = new People(peopleList);  
List<Person> foundPeople = people.getPeople();  
Person person = foundPeople.get(0);  
person.setName("4"); // 컴파일 에러!

이제 정말 끝이 보인다.
하지만, 아직도 디테일이 남아있다.
People의 생성자로 주어지는 List 가 변경된다면?

다음과 같이 불변 객체라고 생각했던 People의 상태가 변경됐다.

List<Person> peopleList = new ArrayList<>();  
peopleList.add(new Person("1"));  
peopleList.add(new Person("2"));  
peopleList.add(new Person("3"));  
  
People people = new People(peopleList);  
peopleList.add(new Person("4"));  
  
System.out.println(people.getPeople()); // [1, 2, 3, 4]

따라서 People의 생성자에 new 키워드로 새로운 참조를 만들어 주어야 한다.

public class People {  
	...
	
    public People(List<Person> people) {  
        this.people = new ArrayList<>(people);  
    }
    
    ...
}
List<Person> peopleList = new ArrayList<>();  
peopleList.add(new Person("1"));  
peopleList.add(new Person("2"));  
peopleList.add(new Person("3"));  
  
People people = new People(peopleList);  
peopleList.add(new Person("4"));  
  
System.out.println(people.getPeople()); // [1, 2, 3]

이렇게 해서 우리는 People 이라는 클래스를 불변 객체로 만들 수 있다.

따라서 3번 규칙 클래스에서 사용되는 모든 가변 객체를 안전하게 캡슐화되도록 한다. 는 클래스에 있는 필드 뿐만 아니라 해당 필드가 참조하고 있는 모든 객체를 불변으로 만들이어야 비로소 불변 객체 라고 부를 수 있다.

또한 클래스에도 final 키워드를 붙여, 상속을 금지하여 재정의까지 금지해야 예상치 못한 상태의 변경을 막을 수 있다.

이렇게 불변 객체의 정의와 만드는 방법을 간단하게 알아봤다.

profile
꾸준히 성장하고 싶은 사람

0개의 댓글