"헤드 퍼스트 디자인 패턴(개정판)"을 읽고 정리한 내용입니다.
9. 컬렉션 잘 관리하기
반복자 패턴
반복자 패턴(Iterator Pattern)은 객체의 구현을 노출하는 것 없이 객체의 모든 요소들을 순차적으로 접근하는 방법을 제공한다.
디자인 원칙
- 애플리케이션에서 달라지는 부분을 식별하고 불변으로부터 그것들을 분리해라. (Identify the aspects of your application that vary and separate them from what stays the same.)
- 달라지는 부분을 찾아서 나머지 코드에 영향을 주지 않도록 캡슐화(encapsulate)한다. 그러면 나중에 바뀌지 않는 부분에는 영향을 미치지 않고 그 부분만 고치거나 확장할 수 있다.
- 구현이 아니라 인터페이스에 프로그래밍해라. (Program to an interface, not an implementation.)
- 특정 행동만을 목적으로 하는 클래스의 집합을 만들고, 이 클래스에서 구현한다. 그러면 슈퍼 클래스에서 구체적으로 구현하거나 서브 클래스 자체에서 별도로 구현하지 않아서 특정 구현에 의존하지 않는다.
- 상속보다는 구성이 더 낫다. (Favor composition over inheritance.)
- 여기서 구성(composition)은 행동을 상속받는 것 대신에, 올바른 행동 객체로 구성되어 행동을 부여받는 것을 말한다.
- 구성을 사용하면 유연성을 향상 시킬 수 있다. 알고리즘군을 별도의 클래스로 캡슐화 할 수 있을 뿐만 아니라, 구성 요소로 사용하는 객체에서 올바르게 행동 인터페이스를 구현하기만 한다면 실행 중에(runtime) 동적으로 행동을 바꿀 수도 있다.
- 상호작용하는 객체들 사이에는 느슨한 결합을 사용하도록 노력해라. (Strive for loosely coupled designs between objects that interact.)
- 느슨한 결합(Loose Coupling)은 객체들 사이의 종속성이 적다는 뜻이다. 즉, 객체들 사이의 유연성이 좋아진다. 대표적인 예로 "옵저버 패턴"이 있다.
- 주제(Subject)는 옵저버가 특정 인터페이스(Observer 인터페이스)를 구현한다는 사실만 알고있다. 즉, 주제는 옵저버가 무엇을 하는지 알 필요가 없다.
- 옵저버는 언제든지 새로 추가할 수 있다. (제거도 마찬가지이다.)
- 새로운 타입의 옵저버를 추가할 때도 주제를 수정할 필요가 없다.
- 주제와 옵저버는 서로 독립적으로 재사용할 수 있다.
- 주제나 옵저버가 달라져도 서로에게 영향을 미치지 않는다.
- 느슨한 결합(Loose Coupling)은 객체들 사이의 종속성이 적다는 뜻이다. 즉, 객체들 사이의 유연성이 좋아진다. 대표적인 예로 "옵저버 패턴"이 있다.
- 클래스는 확장에는 열려있어야 하지만, 변경에는 닫혀 있어야 한다. (Classes should be open for extension, but closed for modification.)
- 이 원칙은 OCP(Open-Closed Principle, 개방-폐쇄 원칙)이다.
- 기존 코드를 변경하지 않고 확장으로 새로운 행동을 추가해야 한다는 말이다.
- 추상화에 의존해라. 구상 클래스에 의존하지 마라.(Depend upon abstractions. Do not depend upon concrete classes.)
- 이 원칙은 DIP(Dependency Inversion Principle, 의존성 뒤집기 원칙)이다.
- 이 원칙을 지키기 위한 가이드 라인은 아래와 같다.
- 변수에 구상 클래스의 레퍼런스를 저장하지 마라.
- 구상 클래스에서 유도된 클래스를 만들지 마라.
- 베이스 클래스에 이미 구현되어 있는 메소드를 오버라이드하지 마라.
- 가까운 친구에게만 말해라. (Talk only to your immediate friends.)
- 이 원칙은 디미터의 법칙(Law of Demeter), 최소 지식 원칙(Principle of Least Knowledge)이다.
- 객체 간의 상호작용을 최소화하여 결합도를 낮춰야 한다는 말로, 체인 호출(점(dot)으로 계속해서 호출하는 것)을 피하라는 것이다.
- 이 원칙을 지키기 위한 가이드 라인은 아래와 같다.
- 객체 자신에 대해서 상호작용.
- 메소드에 매개변수로 전달된 객체에 대해서 상호작용.
- 메소드에서 생성하거나 인스턴스를 만든 객체에 대해서 상호작용.
- 객체에 속하는 구성 요소에 대해서 상호작용.
- 이 원칙은 디미터의 법칙(Law of Demeter), 최소 지식 원칙(Principle of Least Knowledge)이다.
- 우리에게 연락하지 마라. 우리가 너에게 연락할 것이다. (Don't call us, we'll call you.)
- 이 원칙은 할리우드 원칙(Hollywood Principle)이다.
- 상위 레벨에서 하위 레벨을 호출하지, 하위 레벨에서 상위 레벨을 호출하면 안된다는 말이다.
- 한 클래스는 오직 한가지 이유로만 바뀌어야 한다.
- 이 원칙은 단일 책임 원칙(Single Responsibility Principle)이다.
반복자 패턴 예시
public interface Iterator {
boolean hasNext();
MenuItem next();
}
public class DinerMenuIterator implements Iterator {
MenuItem[] items;
int position = 0;
public DinerMenuIterator(MenuItem[] items) {
this.items = items;
}
public MenuItem next() {
MenuItem menuItem = items[position];
position = position + 1;
return menuItem;
}
public boolean hasNext() {
if (position >= items.length || items[position] == null) {
return false;
} else {
return true;
}
}
}
public class PancakeHouseMenuIterator implements Iterator {
List<MenuItem> items;
int position = 0;
public PancakeHouseMenuIterator(List<MenuItem> items) {
this.items = items;
}
public MenuItem next() {
MenuItem item = items.get(position);
position = position + 1;
return item;
}
public boolean hasNext() {
if (position >= items.size()) {
return false;
} else {
return true;
}
}
}
public class DinerMenu implements Menu {
// 생략
public Iterator createIterator() {
return new DinerMenuIterator(menuItems);
}
}
public class PancakeHouseMenu implements Menu {
// 생략
public Iterator createIterator() {
return new PancakeHouseMenuIterator(menuItems);
}
}
컴포지트 패턴
컴포지트 패턴(Composite Pattern)은 부분-전체 계층구조를 표현하기 위해서 객체를 트리 구조로 구성하도록 한다. 컴포지트 패턴은 클라이언트가 개별 객체들과 구성 객체들을 균등하게 다룰 수 있도록 한다.
컴포지트 패턴 예시
public abstract class MenuComponent {
public void add(MenuComponent menuComponent) {
throw new UnsupportedOperationException();
}
public void remove(MenuComponent menuComponent) {
throw new UnsupportedOperationException();
}
public MenuComponent getChild(int i) {
throw new UnsupportedOperationException();
}
public String getName() {
throw new UnsupportedOperationException();
}
public String getDescription() {
throw new UnsupportedOperationException();
}
public double getPrice() {
throw new UnsupportedOperationException();
}
public boolean isVegetarian() {
throw new UnsupportedOperationException();
}
public void print() {
throw new UnsupportedOperationException();
}
}
public class MenuItem extends MenuComponent {
String name;
String description;
boolean vegetarian;
double price;
public MenuItem(String name,
String description,
boolean vegetarian,
double price)
{
this.name = name;
this.description = description;
this.vegetarian = vegetarian;
this.price = price;
}
// 생략
}
import java.util.Iterator;
import java.util.ArrayList;
public class Menu extends MenuComponent {
ArrayList<MenuComponent> menuComponents = new ArrayList<MenuComponent>();
String name;
String description;
public Menu(String name, String description) {
this.name = name;
this.description = description;
}
// 생략
}