The Original Sin
class Person{ private List<Course> cources = new ArrayList<>(); public List<Course> getCources{ return cources; } }
객체 지향 개발자들은 캡슐화를 적극 권장하는데 컬렉션을 다룰 때는 곧잘 실수를 저지르곤 한다.
컬렉션 변수로의 접근을 캡슐화 하면서 게터가 컬렉션 자체(
courses
)를 반환 하도록 한다면, 그 컬렉션을 감싼 클래스 (Person
)가 눈치채지 못하는 상태에서 컬렉션의 원소들이 바뀌어버릴 수 있다.1. 컬렉션 변경자 메서드 제공
class Person{ private List<Course> cources = new ArrayList<>(); public List<Course> getCources{ return cources; } public addCource(Course course){ cources.add(course); } public removeCource(Course course){ cources.remove(course); } }
- 모든 팀원이 원본 모듈(클래스) 밖에서는 컬렉션을 수정하지 않는 습관을 가지고 있다면 이런 메서드를 제공하는 것 만으로 충분
- 하지만 실수 한 번이 굉장히 찾기 어려운 버그로 이어질 수 있으니 습관에 의존하는 방식은 바람직 하지 않다.
2. 내부 컬렉션을 직접 수정 하지 못하게 막기
a. 절대로 컬렉션 값을 반환하지 않기
class Person{ private List<Course> cources = new ArrayList<>(); public addCource(Course course){ cources.add(course); } public removeCource(Course course){ cources.remove(course); } }
- 부가적인 코드가 너무 많아지는 단점
- 컬렉션 연산들을 조합해 쓰기도 어려워 진다.
- @See Also)
b. 컬렉션을 읽기전용으로 제공
- 방법 1 - 컬렉션의 읽기 전용 프락시를 반환
- 읽는 연산은 그대로 전달, 쓰기는 예외를 던진다. (UnmodifiableCollection)
- @)See Also
- 방법 2 - iterator나 열거형 객체를 기반으로 컬렉션을 조합하는 라이브러리
- iterator에서 내부 컬력선을 수정 할 수 없게 함
- 방법 3 - 컬렉션 게터를 제공하되 내부 컬렉션의 복제본을 반환 : 가장 널리 쓰이는 방식
- 복제본을 수정해도 캡슐화된 원본 컬렉션에는 아무런 영향을 주지 않는다.
- 컬력션을 수정하면 원본 데이터가 수정 될 것이라 기대한 프로그래머는 당황할 수 있지만 여러 코드베이스에서 많은 프로그래머가 널리 사용하는 방식이라 크게 문제되지 않음
- 컬렉션이 상당히 크다면 성능문제가 발생할 수 있다.
⭐ 여기서 중요한 점은 코드베이스에서 일관성을 주는 것이다. 앞에 나온 방식 중에서 한 가지만 적용해서 컬렉션 접근 함수의 동작 방식을 통일해야 한다.