어댑터 패턴(Adapter pattern)과 퍼사드 패턴(Pacade pattern)


Head First - Design Patterns
(에릭 프리먼, 엘리자베스 프리먼, 케이시 시에라, 버트 베이츠 저 | 서환수 역)
을 읽고 정리한 내용입니다.


어댑터 패턴(Adapter pattern)

어댑터 패턴(Adapter pattern)
: 한 클래스의 인터페이스를 클라이언트에서 사용하고자 하는 다른 인터페이스로 변환한다.
어댑터를 이용하면, 인터페이스 호환성 문제 때문에 같이 쓸 수 없는 클래스들을
연결해서 쓸 수 있다.

클래스 다이어그램

1. 객체 어댑터

  • Client에서는 Target 인터페이스를 볼 수 있다.
  • Adapter에서 Target 인터페이스를 구현한다.
  • AdapterAdaptee로 구성되어 있다.
  • 모든 요청이 Adaptee로 위임된다.

어댑티를 새로 바뀐 인터페이스로 감쌀 때 객체 구성(composition)을 활용한다.

  • 장점: 어댑티의 모든 서브 클래스에서도 어댑터를 쓸 수 있다.
    클라이언트를 특정 구현이 아니라 인터페이스에 연결시킨다.
  • 따라서, 타겟 인터페이스만 제대로 지키면 나중에 다른 구현을 추가하는 것도 가능하다.

2. 클래스 어댑터

  • 클래스 어댑터를 사용하려면, 다중 상속이 필요한데 자바에서는 다중 상속이 불가능하다.
  • 객체를 한 두 개 더 만들더라도 객체 구성(composition)을 활용하자.

어댑터 실전 예제

Enumeration과 Iterator

Enumeration은 Collection 객체의 멤버 객체들을 나열하기 위한 인터페이스이다.
일반적으로 VectorHashtable 객체에서 제공하는 메소드로 그 객체를 생성한다.
Enumeration은 Collection Framework가 나온 이후 (Java 1.2 이후)부터 사용하지 않고,
Enumeration 대신 Iterator를 사용한다.

cf)
Vector -> ArrayList
Hashtable -> HashMap
Enumeration -> Iterator

자바에서의 동기화

VectorHashtable와 같은 클래스들은 자체적으로 동기화 처리가 되어있다.
그러나 멀티쓰레드 프로그래밍이 아닌 경우에는 불필요하기 때문에 성능을 떨어뜨리는 요인이 된다.
그래서 새로 추가된 ArrayListHashMap과 같은 컬렉션은 동기화를 자체적으로 처리하지 않고
필요한 경우에만 java.util.Collections 클래스의 동기화 메서드를 이용해서 동기화 처리가 가능 하도록 한다.

ArrayList, HashSet, HashMap 은 멀티스레드 환경에서 안전하지 않다.
Collections.synchronizedList를 사용하면, 동기화된 컬렉션을 리턴한다.

1
2
3
4
5
6
7
// List를 동기화된 List로 리턴
List list = Collections.synchronizedList(new ArrayList());
// Map을 동기화된 Map으로 리턴
Map map = Collections.synchronizedMap(new HashMap());

// Set을 동기화된 Set으로 리턴
Set set = Collections.synchronizedSet(new HashSet());

동기화된(synchronized) 컬렉션은 멀티 스레드 환경에서
하나의 스레드가 요소를 안전하게 처리하도록 도와주지만,
전체 요소를 빠르게 처리하지는 못한다.
스레드가 병렬적으로 요소들을 처리할 수 없어서 스레드가 작업을 할 때 락이 발생하기 때문이다.

java.util.concurrent 패키지를 이용하자.

자바에서는 멀티스레드 환경에서 안전하면서도 스레드가 병렬적으로 작업을 처리할 수 있도록
java.util.concurrent 패키지에서
CopyOnArrayList, ConcurrentHashMap, ConcurrentLinkedQueue를 제공한다.

  • CopyOnWriteArrayList
    : read (select) 시는 아무런 동기화 문제가 없기 때문에 놔두고
    변경이 일어날 때, 객체를 clone 해서 다루자는 전략이다.
    따라서 읽기행위가 많이 일어나는 곳에서 사용하기 좋다.

  • ConcurrentHashMap
    : HashMap을 thread-safe 하도록 만든 클래스가 ConcurrentHashMap이다.
    하지만 HashMap과는 다르게 key, value에 null을 허용하지 않는다.
    또한 putIfAbsent라는 메소드를 가지고 있다.

-ConcurrentLinkedQueue
: ConcurrentLinkedQueue는 큐에 꺼낼 원소가 없다면 즉시 리턴하고 다른 일을 수행하러 간다.
따라서, ConcurrentLinkedQueue는 생산자-소비자 producer-consumer 모델에서
소비자가 많고 생산자가 하나인 경우에 사용하면 좋다.


EnumerationIterator 어댑터 코드

Enumerator 인터페이스를 사용하는 구형 코드가 있다.
새로 만드는 코드에서는 Iterator만 사용한다.
이런 경우에 어댑터 패턴을 적용하면 좋다.

어댑터 패턴에 익숙해지기 위해
아래의 EnumerationIterator 어댑터 코드 예제는 책을 안보고 직접 구현해보도록 하자.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/*
Enumeration을 Iterator에 적응시키기 위해
Iterator 인터페이스를 구현한다.
겉에서 볼 때는 Iterator로 보여야 한다.
*/
public class EnumerationIterator implements Iterator {
Enumeration enum;

public EnumerationIterator(Enumeration enum) {
this.enum = enum;
}

public boolean hasNext() {
return enum.hasMoreElements();
}

public Object next() {
return enum.nextElement();
}

public void remove() {
throw new UnsupportedOperationException();
}
}

퍼사드 패턴(Pacade pattern)

커맨드 패턴 VS 퍼사드 패턴

  • 커맨드 패턴(Command pattern)

    • 메서드 1개만 노출
    • 캡슐화해서 접근하지 못함
    • tight
  • 퍼사드 패턴(Pacade pattern)

    • 메서드 여러 개 노출
    • 서브 시스템을 캡슐화하지 않음
    • loose
    • 스마트 커맨드와 비슷

어댑터 패턴 VS 퍼사드 패턴

  • 공통점: 여러 개의 클래스를 감쌀 수 있다.
  • 차이점
    • 어댑터 패턴: 인터페이스를 다른 인터페이스로 변환하기 위한 용도
    • 퍼사드 패턴: 인터페이스를 단순화시키기 위한 용도

      어댑터 패턴과 퍼사드 패턴의 차이는 감싸는 클래스 개수가 아니라,
      용도의 차이다.

퍼사드 패턴은 Spring MVC Service와 비슷하다.

  • 압축된 인터페이스를 제공한다.
  • 로직은 각각 도메인들이 가지고 있고, 서비스에서는 호출해서 가져온다.
  • 서비스에서는 로직을 가지면 안된다.

퍼사드 패턴(Pacade pattern)의 정의

퍼사드 패턴(Pacade pattern)
: 어떤 서브시스템의 일련의 인터페이스에 대한
통합된 인터페이스를 제공한다.
퍼사드에서 고수준 인터페이스를 정의하기 때문에
서브시스템을 더 쉽게 사용할 수 있다.

디미터 법칙(최소 지식 원칙)

디자인 원칙
정말 친한 친구하고만 얘기하라.

  • ex1) 디미터 법칙(최소 지식 원칙)을 따르지 않는 경우
1
2
3
4
5
6
public float getTemp() {
// station에서 thermometer라는 객체를 받아서
// 그 객체의 getTemperature() 메서드를 직접 호출해야 한다.
Thermometer thermometer = station.getThermometer();
return thermometer.getTemperature();
}
  • ex2) 디미터 법칙(최소 지식 원칙)을 따르는 경우
1
2
3
4
5
6
public float getTemp() {
// 디미터 법칙(최소 지식 원칙)을 적용해서 Station 클래스에
// thermometer에 요청을 해주는 메서드를 추가했다.
// 이렇게 하면, 의존해야 하는 클래스의 개수를 줄일 수 있다.
return station.getTemperature();
}

더 공부할 Keyword


참고 링크 & 서적