어댑터 패턴과 퍼사드 패턴을 알아보자.
어댑터 패턴, 퍼사드 패턴
어댑터 패턴
특정 클래스 인터페이스를 클라이언트에서 요구하는 다른 인터페이스로 변환한다. 인터페이스가 호환되지 않아 같이 쓸 수 없었던 클래스를 사용할 수 있게 도와준다.
퍼사드 패턴
서브시스템에 있는 일련의 인터페이스를 통합 인터페이스로 묶어 준다. 또한 고수준 인터페이스도 정의하므로 서브시스템을 더 편리하게 사용할 수 있다.
어댑터 패턴
소프트웨어 시스템에 새로운 클래스 라이브러리를 사용하는 상황을 가정해 보자. 라이브러리가 사용하는 인터페이스가 기존에 사용하는 인터페이스와 다른 경우에 어떻게 해야 할까? 기존 코드를 바꾸거나 라이브러리 클래스를 변경하는 작업에 많은 노력이 필요하다. 이를 위해 라이브러리가 사용하는 인터페이스를 기존에 사용하던 인터페이스에 적응시켜 주는 클래스를 만들면 된다. 이 적응시켜 주는 클래스를 어댑터 클래스라고 부른다. 어댑터는 클라이언트로부터 요청을 받아서 새로운 업체에게 제공하는 클래스를 클라이언트가 받아들일 수 있는 형태의 요청으로 변환해 주는 중개인 역할을 한다.
클래스 다이어그램은 다음과 같다.

클라이언트에서 어댑터를 사용하는 방법은 다음과 같다.
1. 클라이언트에서 다킷 인터페이스로 메서드를 호출해서 어댑터에 요청을 보낸다.
2. 어댑터는 어댑티 인터페이스로 그 요청을 어댑터에 관한(하나 이상의) 메서드 호출로 변환한다.
3. 클라이언트는 호출 결과를 받긴 하지만 중간에 어댑터가 있다는 사실을 모른다.
어댑터 패턴 구현
클래스 다이어그램을 참고하여 코드로 구현해보도록 하겠다.
클라이언트가 사용할 인터페이스이다.
public interface TargetInterface {
public void targetARequest1();
public void targetARequest2();
}
모든 요청을 실제로 실행할 Adaptee 클래스이다.
public class Adaptee {
public void AdapteeRequest() {
System.out.println("adaptee request");
}
public void AdapteeMethod() {
System.out.println("adaptee method");
}
}
Target과 Adaptee를 연결할 Adapter 클래스이다. Target을 상속받는다.
public class Adapter implements TargetInterface {
private Adaptee adaptee;
public Adapter(Adaptee adaptee) {
this.adaptee = adaptee;
}
@Override
public void targetARequest1() {
adaptee.AdapteeRequest();
}
@Override
public void targetARequest2() {
adaptee.AdapteeMethod();
}
}
마지막으로 Target을 실행시킬 Client 클래스이다.
public class Client {
public static void main(String[] args) {
TargetInterface targetInterface = new Adapter(new Adaptee());
targetInterface.targetARequest1();
targetInterface.targetARequest2();
}
}
어댑터 패턴 실전 적용
어댑터 패턴을 실제로 적용하는 예제 코드를 살펴보도록 하겠다.
어댑티 인터페이스는 Enumeratio이다. hasMoreElements(), nextElement() 메서드를 갖고 있다. 초기 컬렉션 현식(Vector, Stack, HashTable)에서 다음 항목이 있는지 알려주고, 다음 항목으로 넘어가는 메서드를 담당한다.
타겟 인터페이슨느 Iterator이다. hasNext()는 다음 항목이 있는지 알려주는 메서드, next()는 다음 항목을 리턴하는 메서드, remove()는 항목을 제거하는 메서드이다.
어댑터 패턴을 활용해 Iterator 인터페이스로 Enumeration을 사용할 수 있도록 만들어 보자. 클래스 다이어그램은 다음과 같다.

코드는 다음과 같다.
import java.util.Enumeration;
import java.util.Iterator;
public class EnumerationIterator implements Iterator<Object> {
private Enumeration<?> enumeration;
public EnumerationIterator(Enumeration<?> enumeration) {
this.enumeration = enumeration;
}
@Override
public boolean hasNext() {
return enumeration.hasMoreElements();
}
@Override
public Object next() {
return enumeration.nextElement();
}
@Override
public void remove() {
throw new UnsupportedOperationException();
}
}
Enumeratino이 읽기 전용 인터페이스이기 때문에 remove() 기능을 제공하지 않는다. 따라서 UnsupportedOperationException 예외를 발생시켰다.
퍼사드 패턴
퍼사드 패턴(Facade Pattern)이란 서브시스템에 잇는 일련의 인터페이스를 통합 인터페이스로 묶어 준다. 고수준 인터페이스로 정의하므로 서브시스템을 더 편리하게 사용할 수 있다.
퍼사드 패턴의 클래스 다이어그램은 다음과 같다.

퍼사드 패턴은 인터페이스를 단순하게 변경하기 위해 사용한다. 하나 이상의 클래스 인터페이스를 퍼사드로 덮어 서브 시스템의 복잡성을 숨기는 역할을 한다.
코드를 단순하게 구현해 보았다.
서브 시스템
public class SubSystemClassA {
public void request1() {
System.out.println("a 1 request");
}
public void request2() {
System.out.println("a 2 request");
}
}
public class SubSystemClassB {
public void request1() {
System.out.println("b 1 request");
}
public void request2() {
System.out.println("b 2 request");
}
}
public class SubSystemClassC {
public void request1() {
System.out.println("c 1 request");
}
public void request2() {
System.out.println("c 2 request");
}
}
Facade 클래스
package facade;
public class Facade {
private SubSystemClassA subSystemClassA;
private SubSystemClassB subSystemClassB;
private SubSystemClassC subSystemClassC;
public Facade(SubSystemClassA subSystemClassA, SubSystemClassB subSystemClassB, SubSystemClassC subSystemClassC) {
this.subSystemClassA = subSystemClassA;
this.subSystemClassB = subSystemClassB;
this.subSystemClassC = subSystemClassC;
}
public void operateAll() {
subSystemClassA.request1();
subSystemClassA.request2();
subSystemClassB.request1();
subSystemClassB.request2();
subSystemClassC.request1();
subSystemClassC.request2();
}
}
Client 클래스
public class Client {
public static void main(String[] args) {
TargetInterface targetInterface = new Adapter(new Adaptee());
targetInterface.targetARequest1();
targetInterface.targetARequest2();
}
}
퍼사드 패턴과 최소 지식 원칙
퍼사드 패턴에 최소 지식 원칙(Principle Of Least Knowledge)라는 객체지향 원칙이 적용됐다.
최소 지식 원칙이란 객체가 다른 객체에 대해 가능한 한 적은 정보를 알아야 한다는 것을 의미한다. 시스템을 디자인할 때 어떤 객체든 그 객체와 상호작용을 하는 클래스의 개수와 상호작용 방식에 주의를 기울여야 한다는 뜻이다.
최소 지식 원칙을 따르면 여러 클래스가 복잡하게 얽혀 시스템의 한 부분을 변경했을 때 다른 부분까지 줄줄이 고쳐야 하는 상황을 미리 방지할 수 있다. 여러 클래스가 서로 복잡하게 위존하고 있다면 관리하기도 힘들고, 남들이 이해하기 어려운 불안정한 시스템이 만들어진다.
최소 지식 원칙을 지키기 위해 4가지 가이드라인을 지켜야 한다.
1. 객체 자체
2. 메서드에 매개변수로 전달된 객체
3. 메서드를 생성하거나 인스턴스를 마든 객체
4. 객체에 속하는 구성 요소
위 4가지 원칙은 객체는 자신이 포함하는 객체들, 직접적으로 생성한 객체들, 그리고 메서드의 인자로 전달된 객체들에만 메시지를 보내야 한다는 뜻을 의미한다.
최소 지식 원칙을 따르지 않았을 때 코드는 다음과 같다.
public float getTemp() {
// station으로부터 thermometer 객체를 받은 다음, 그 객체의 getTemperatur() 메서드를 직접 호출한다.
Thermometer thermometer = station.getThermometer();
return thermometer.getTemperature();
}
위 코드를 다음과 같이 고쳐야 한다.
public float getTemp() {
// station으로부터 thermometer 객체를 받지 않아 의존해야 하는 클래스를 줄일 수 있다.
return station.getTemperature();
}
다음은 Car 클래스에서 최소 지식 원칙을 따르면서 메서드를 호출하는 방법을 나타내는 코드이다.
public class Car {
Engine engine;
public Car(Engine engine) {
this.engine = engine;
}
public void start(Key key) {
Doors doors = new Doors();
boolean authorized = key.turns();
if(authorized) {
//객체의 구성 요소를 대상으로 메서드를 호출해도 된다.
engine.start();
//객체 내에 있는 메서드는 호출해도 된다.
updateDashboardDisplay():
//직접 생성하거나 인스턴스를 만든 객체의 메서드는 호출해도 된다.
doors.lock();
}
}
public void updateDashboardDisplay() {
//display 갱신
}
}
퍼사드 패턴은 최소 지식 원칙을 실현하는 좋은 방법 중 하나이다. 퍼사드 패턴을 통해 클라이언트는 하위 시스템의 복잡한 구성 요소들과 직접 상호작용하지 않고, 퍼사드 클래스라는 단일 인터페이스를 통해 간접적으로 상호작용하게 된다. 즉 퍼사드 패턴은 복잡한 시스템의 세부 사항을 클라이언트로부터 숨기고, 단순화된 인터페이스를 통해 상호작용을 제공한다. 이를 통해 시스템의 복잡성을 줄이고, 코드의 품질과 가독성을 높일 수 있다.