▪Encapsulate Collection 컬렉션 캡슐화
Problem & Solution
그러나 컬렉션은 다른 데이터 유형에서 사용되는 프로토콜과 약간 다른 프로토콜에 의해 사용되야 한다.
컬렉션에 값을 할당하는 메서드가 있어서는 안된다
이러한 프로토콜은 컬렉션을 적절하게 캡슐화하므로 궁극적으로 소유자 클래스와 클라이언트 코드 간의 연결 정도가 줄어 든다.
Problem
class Course {
public Course(String name, boolean isAdvanced) {
// ...
}
public boolean isAdvanced() {
// ...
}
}
class Person {
private Set courses = new HashSet();
public Set getCourses() {
return Collections.unmodifiableSet(courses);
}
public void initializeCourses(Set arg) {
Assert.isTrue(courses.isEmpty());
courses.addAll(arg);
}
public void addCourse(Course arg) {
courses.add(arg);
}
public void removeCourse(Course arg) {
courses.remove(arg);
}
public int numberOfAdvancedCourses() {
Iterator iter = getCourses().iterator();
int count = 0;
while (iter.hasNext()) {
Course each = (Course) iter.next();
if (each.isAdvanced()) {
count++;
}
}
return count;
}
public int numberOfCourses() {
return courses.size();
}
}
// Client code
Person kent = new Person();
kent.addCourse(new Course("Smalltalk Programming", false));
kent.addCourse(new Course("Appreciating Single Malts", true));
Assert.equals(2, kent.numberOfCourses());
Course refact = new Course("Refactoring", true);
kent.addCourse(refact);
kent.addCourse(new Course("Brutal Sarcasm", false));
Assert.equals(4, kent.numberOfCourses());
kent.removeCourse(refact);
Assert.equals(3, kent.numberOfCourses());
System.out.print("Advanced courses: " + kent.numberOfAdvancedCourses());
Solution
class Course {
public Course(String name, boolean isAdvanced) {
// ...
}
public boolean isAdvanced() {
// ...
}
}
class Person {
private Set courses;
public Set getCourses() {
return courses;
}
public void setCourses(Set arg) {
courses = arg;
}
}
// Client code
Person kent = new Person();
Set s = new HashSet();
s.add(new Course("Smalltalk Programming", false));
s.add(new Course("Appreciating Single Malts", true));
kent.setCourses(s);
Assert.equals(2, kent.getCourses().size());
Course refact = new Course("Refactoring", true);
kent.getCourses().add(refact);
kent.getCourses().add(new Course("Brutal Sarcasm", false));
Assert.equals(4, kent.getCourses().size());
kent.getCourses().remove(refact);
Assert.equals(3, kent.getCourses().size());
Iterator iter = kent.getCourses().iterator();
int count = 0;
while (iter.hasNext()) {
Course each = (Course) iter.next();
if (each.isAdvanced()) {
count++;
}
}
System.out.print("Advanced courses: " + count);
컬렉션을 리턴하는 메소드가 있으면 그 메소드가 읽기전용 뷰를 리턴하도록 만들고, add/remove 메소드를 제공하라.
get 메소드가 컬렉션 자체를 리턴하면 안 된다. 이유는 클라이언트 코드가 컬렉션을 가지고 있는 클래스가 알지 못하는 사이에 컬렉션의 내용을 조작할 수 있기 때문이다. get 메소드는 컬렉션을 조작하지 못하도록 하여 리턴 해야 한다. 또한 set 메소드도 있으면 안 된다. 대신 컬렉션의 요소를 추가, 삭제하는 작업을 제어할 수 있게 해야 한다.
절차
1. 컬렉션 요소 추가 및 삭제 방법을 만든다. 매개 변수에 수집 요소를 받아 들여야한다.
2. 빈 생성자가 클래스 생성자에서 완료되지 않은 경우 필드에 초기 값으로 할당.
3. 수집 필드 설정 기의 호출을 찾는다. setter를 변경하여 요소 추가 및 삭제 작업을 사용하거나 이러한 작업이 클라이언트 코드를 호출하도록한다.
setter는 모든 콜렉션 요소를 다른 콜렉션 요소로 바꿀 때만 사용할 수 있다. 따라서 setter 이름 (Rename Method)을 바꿔서 바꾸는 것이 좋다.
4. 콜렉션이 변경된 콜렉션 게터의 모든 호출을찾는다.
컬렉션에서 요소를 추가 및 삭제하는 새 메서드를 사용하도록 코드를 변경
5. getter를 변경하여 컬렉션의 읽기 전용 표현을 반환
6. 콜렉션 클래스 자체의 내부에서 더 잘 보일 수있는 코드 용 콜렉션을 사용하는 클라이언트 코드를 조사
Benefits
• 컬렉션 필드는 클래스 내에 캡슐화된다. getter가 호출되면 컬렉션을 포함하는 클래스에 대한 지식없이 컬렉션 요소가 우연히 변경되거나 덮어 쓰지 않도록 컬렉션의 복사본을 반환한다.
• 컬렉션 요소가 배열과 같은 기본 유형 안에 포함되어 있으면 컬렉션 작업에보다 편리한 메서드를 만들 수 있다.
• 컬렉션 요소가 비 프리미티브 컨테이너 (표준 컬렉션 클래스)에 포함되어 있으면 컬렉션을 캡슐화하여 컬렉션의 원치 않는 표준 메소드에 대한 액세스를 제한 할 수 있다. (예 : 새 요소 추가 제한).