흔히 개발할때 컬렉션 프레임워크를 사용해서 데이터를 처리할때 for 문을 사용했었다.
그렇게 개발하던 도중에 다음과 같은 로직이 필요했고 지난번과 같이 for 문으로 처리할 계획을 했다.
요구사항
도서관 에서 '칼의 노래'는 제거하고 나머지는 2 를 붙이는 요구사항이 왔으면 어떻게 할 것인가?
List<Book> books;
@Before
public void setUp() throws Exception {
books = new ArrayList<>();
Book book = new Book("해리포터");
books.add(book);
Book book2 = new Book("반지의 제왕");
books.add(book2);
Book book3 = new Book("헝거게임");
books.add(book3);
Book book4 = new Book("종이여자");
books.add(book4);
Book book5 = new Book("칼의노래");
books.add(book5);
}
@Test(expected = ConcurrentModificationException.class)
public void forEachTest() {
for(Book book : books) {
if(book.getName().equals("칼의노래")){
books.remove(book);
} else {
String name = book.getName();
book.setName(name + "2");
}
}
}
다음과 같이 생각할 수도 있다.
하지만 위와 같은 exception 이 발생한다.
이를 해결하려고 다음과 같은 해결책을 제시했다.
@Test
public void iteratorTest(){
Iterator<Book> bookIterator = books.iterator();
while(bookIterator.hasNext()){
Book book = bookIterator.next();
if(book.getName().equals("칼의노래")){
bookIterator.remove();
} else {
String name = book.getName();
book.setName(name + "2");
}
}
Assert.assertEquals(4, books.size());
}
여기서 iterator 에 대한 관심을 가지게 되었다.
그때 때마침 iterator 디자인 패턴에 대해서 공부하게 되었다.
내가 만드는 구조는 다음과 같다.
public interface Iterator<T> {
boolean hasNext();
T next();
}
Iterator 인터페이스를 만들고
abstract method 를 정의한다.
public interface Aggregate<T> {
Iterator<T> iterator();
}
그리고 해당 Iterator 를 구현하는 interface 를 만든다.
/**
*
* 서가 ( 도서관 ) 를 나타내는 클래스이다.
*
*
*/
public class BookShelf implements Aggregate<Book> {
private Book[] books;
private int last = 0;
public BookShelf(int maxSize){
this.books = new Book[maxSize];
}
public Book get(int index){
return books[index];
}
public void add(Book book){
this.books[last] = book;
last++;
}
public int size(){
return last;
}
@Override
public Iterator<Book> iterator() {
return new Itr();
}
/**
* 비정적 멤버 클래스의 인스턴스와 바깥 인스턴스 사이의 관계는 멤버 클래스가 인스턴스화될 때 확립되며, 더 이상 변경할 수 없다.
* 이 관계는 바깥 클래스의 인스턴스 메서드에서 비정적 멤버 클래스의 생성자를 호출할 때 자동으로 만들어지는게 보통이다.
*
* 멤버 클래스에서 바깥 인스턴스에 접근할 일이 없다면 무조건 static 을 붙여서 정적 멤버 클래스로 만들자.
*
* static 을 생략하면 바깥 인스턴스로의 숨은 외부 참조를 갖게 된다.
* -> 이 참조를 저장하려면 시간과 공간이 소비된다.
* 그리고 가비지 컬렉션이 바깥 클래스의 인스턴스를 수거하지 못하는 메모리 누수가 생길 수 있다는 점이다.
*
* 참조가 눈에 보이지 않으니 문제의 원인을 찾기 어려워 때떄로 심각한 문제를 발생시킬 수 있다.
*
* 결론
*
* 멤버 클래스의 인스턴스 각각이 바깥 인스턴스를 참조한다면 비정적으로,
* 그렇지 않으면 정적으로 만들자.
*/
private class Itr implements Iterator<Book> {
int index;
@Override
public boolean hasNext() {
return index != books.length;
}
@Override
public Book next() {
if(index >= books.length){
// runtime exception 인 이유 설명
throw new NoSuchElementException();
}
Book book = books[index];
index++;
return book;
}
}
}
마지막으로 도서관 class 를 구현하면 iterator 디자인 패턴이 완성된다.
여기서 Iterator 는 어디서 본적이 있지 않은가 ?
실제로 우리가 흔히 사용하는 Collection 프레임워크가 Aggregate 역할이 있다.
즉 Collection framework 에 iterator 디자인 패턴이 적용된 것이다.
그렇다면 왜 Iterator 를 사용할까?
그냥 배열을 이용하면 안되나? 라고 의문을 가질 수 있다.
한번 생각해보자 Iterator interface 를 구현한다. -> 분리되어 있다. 즉 분리되어 하나씩 센다.
ArrayList 와 분리되어 하나씩 센다.
테스트 코드
package main.service;
import main.model.Book;
import static org.junit.Assert.*;
import org.junit.Before;
import org.junit.Test;
public class BookShelfTest {
private BookShelf bookShelf;
@Before
public void setUp() throws Exception {
bookShelf = new BookShelf(5);
bookShelf.add(new Book("해리포터"));
bookShelf.add(new Book("반지의 제왕"));
bookShelf.add(new Book("헝거게임"));
bookShelf.add(new Book("종이여자"));
bookShelf.add(new Book("칼의노래"));
}
@Test
public void iterator_Test(){
Iterator<Book> bookIterator = bookShelf.iterator();
while(bookIterator.hasNext()){
Book book = bookIterator.next();
System.out.println(book.getName());
}
}
}
에서 우리가 사용한 작업은 BookShelf 내부에 있는 Array 를 직접 건들지 않으면서 작업처리를
Iterator 가 제공하는 메소드 hasNext 와 next 를 사용해서 한다.
즉 BookShelf 의 구현에는 의존하지 않는다는 이야기가 된다.
우리가 ArrayList 를 사용하든 LinkedList 를 내부적으로 바꾸든 내부적으로 배열을 사용하든 백터를 사용하든 iterator 로 구현된 로직은 영향을 받지 않는다는 이야기가 된다.
그렇기 때문에 관심사의 분리가 일어나고 OCP 적인 코드가 되는 디자인 패턴이 되는 것이다.
'개발 > java' 카테고리의 다른 글
객체의 생성 (0) | 2020.07.09 |
---|---|
빌더로 생성자 대체하기 (0) | 2020.07.06 |
JPA 2차 캐시란 (0) | 2020.04.26 |
JPA 락 (0) | 2020.04.25 |
JPA 기초 프록시란 무엇인가 (0) | 2020.04.12 |
댓글