[GoF] Iterator 패턴

패턴명칭

Iterator

필요한 상황

배열과 같이 동일한 형태의 데이터를 여러 개 저장하고 있는 저장소(Aggregator)가 있다. 이 저장소에 저장된 데이터를 순서대로 접근하기 위한 패턴이다. 순서대로 접근하기 위한 방법은 저장소의 종류와는 상관없이 동일한 방식(API)을 제공한다.

예제 코드

Aggregator와 Iterator 인터페이스는 저장소의 형태와 종류에 상관없이 저장소를 구성하는 데이터를 순차적으로 모두 접근하기 위한 API를 제공한다. 이 인터페이스를 구성하는 Table과 RowIterator는 저장소에 대한 특화된 구현 클래스이다. Row 역시 저장소를 구성하는 특화된 데이터 구조에 대한 클래스이다. 이들 클래스를 사용하는 예제는 다음과 같다.

package pattern;

public class Main {
	public static void main(String[] args) {
		Table table = new Table();
		
		table.add(new Row("Jane", 27));
		table.add(new Row("Suji", 35));
		table.add(new Row("Tom", 19));
		table.add(new Row("Robin", 43));
		table.add(new Row("Robert", 58));
		
		Iterator it = table.iterator();
		while(it.next()) {
			Row row = (Row)it.current();
			System.out.println(row);
		}
	}
}

Aggregator 인터페이스는 다음과 같다.

package pattern;

public interface Aggregator {
	Iterator iterator();
}

Itertor 인터페이스는 다음과 같다.

package pattern;

public interface Iterator {
	boolean next();
	Object current();
}

next는 다음 구성 데이터로 이동하고 true를 반환한다. 만약 다음 데이터가 존재하지 않으면 false를 반환한다. current는 현재 Itertor가 가르키고 있는 현재의 데이터를 반환한다. Aggregator를 구현하는 Table 클래스는 다음과 같다.

package pattern;

import java.util.LinkedList;

public class Table implements Aggregator {
	private LinkedList<Row> table = new LinkedList<Row>();
	
	public Table() {
		//.
	}
	
	public Row get(int index) {
		return table.get(index);
	}
	
	public void add(Row row) {
		table.addLast(row);
	}
	
	public int getLength() {
		return table.size();
	}
	
	@Override
	public Iterator iterator() {
		return new RowIterator(this);
	}
}

Iterator를 구현하는 RowIterator 클래스는 다음과 같다.

package pattern;

public class RowIterator implements Iterator {
	private Table table;
	private int index;
	
	public RowIterator(Table table) {
		this.table = table;
		this.index = -1;
	}
	
	@Override
	public boolean next() {
		index++;
		return index < table.getLength();
	}

	@Override
	public Object current() {
		return table.get(index);
	}
}

저장소를 구성하는 실제 데이터에 대한 Row 클래스는 다음과 같다.

package pattern;

public class Row {
	private String name;
	private int age;
	
	public Row(String name, int age) {
		this.name = name;
		this.age = age;
	}
	
	public String getName() {
		return name;
	}
	
	public int getAge() {
		return age;
	}
	
	public String toString() {
		return "(" + name + ", " + age + ")";
	}
}

실행 결과는 다음과 같다.

(Jane, 27)
(Suji, 35)
(Tom, 19)
(Robin, 43)
(Robert, 58)
이 글은 소프트웨어 설계의 기반이 되는 GoF의 디자인패턴에 대한 강의자료입니다. 완전한 실습을 위해 이 글에서 소개하는 클래스 다이어그램과 예제 코드는 완전하게 실행되도록 제공되지만, 상대적으로 예제 코드와 관련된 설명이 함축적으로 제공되고 있습니다. 이 글에 대해 궁금한 점이 있으면 댓글을 통해 남겨주시기 바랍니다.

Kotlin, Null 검사

이미 20년 내외로 개발자들에게 포인터나 참조에 대한 null은 악몽이라고 했다. 사람은 정확하게 작동하는 기계가 아니므로, 그날의 컨디션이나 스트레스 따위로 실수는 언제든 할 수 있다는 점에서 null에 대한 참조는 분명 악몽의 작은 불씨임에는 틀림이 없다.

Kotlin은 참조에 대한 null 값을 최대한 방지하고자 값에 대해 null 값을 가질 수 있는 경우와 null 값을 가지지 못하는 경우로 분명하게 분리를 했으며, 각각에 대해 Nullable과 Non-Null이라는 타입이며 기본적이 Kotlin에서의 값들은 Non-Null 타입이다.

var a:Int = null

위의 코드는 Null can not be a value of a non-null type Int라는 에러가 발생한다. 이를 방지하려면 다음처럼 추가적인 작은 코드가 필요하다.

var a:Int? = null

Null 검사에 대한 Kotlin의 문법에는 ?., ?:, !!, ?.let{} 등이 있다. 하나씩 간략하게 정리한다.

?. 연산자

?.는 값이 null이면 null을 반환하고 구문을 종료하고 null이 아니면 계속 구문을 실행한다는 것이다.

var a:Int? = 10
println(a?.toString())

a = null
println(a?.toString())

실행 결과는 다음과 같다.

10
null

>?: 연산자

엘비스 프레슬리의 헤어 스타일을 닮았다고해서 엘비스 연산자랜다. 장난하냐? 여튼, 이 연산자는 값이 null일 경우에 대한 대체값을 지정하기 위한 연산자이다.

var a:Int? = 10
println(a?:"NULL")

a = null
println(a?:"NULL")

결과는 다음과 같다.

10
NULL

!! 연산자

이 연산자는 값이 null일 수 없으니 걱정말고 실행하라는 의미이다.

var a:Int? = 10
println(a!!.toString())

a = null
println(a!!.toString())

첫번째 출력은 10이고, 두번째에서는 값이 null인데 toString() 함수를 호출하고 있어 NullPointerException이 똭! 악몽의 작은 불씨~ 퍽!

?.let{} 구문

이 구문은 값이 null이 아닐 경우 여러 문장을 실행할 수 있는 구문을 제공한다.

var a:Int? = 10
a?.let {
    println("The world is good place to live.")
    println("are you kidding me?")
}  

a = null
a?.let {
    println("The world is good place to live.")
    println("I agree with you.")
}  

실행 결과는 다음과 같다.

The world is good place to live.
are you kidding me?