Computer Science/디자인패턴

[Head First Design Patterns] 07 어댑터 패턴과 퍼사드 패턴

계속지나가기 2020. 11. 7. 20:33
반응형

(본 강의 노트는 한빛 미디어의 [Head First Design Patterns]책을 기반으로 하고 있습니다)

 

07 어댑터 패턴과 퍼사드 패턴

Adapter Pattern & Facade Pattern

07-01. Adapter Pattern

목적

클래스의 인터페이스를 클라이언트가 원하는 형태의 또다른 인터페이스로 변환. 어댑터는 호환되지 않는 인터페이스 때문에 동작하지 않는 클래스들을 함께 동작할 수 있도록 만들어줌 (aka. Wrapper in Java)

어댑터 패턴 정의

디자인 패턴 요소

요소

설명

이름

어댑터(Adapter)

문제

사용 객체의 API가 서로 다름

해결방안

함수를 변환하는 객체를 중간에 넣음

결과

변경 최소화

객체를 감싸는 역할을 함(Object Wrapping)

- 서로 호환되지 않는 두 개의 인터페이스를 연결하는 작업

- 서로 다른 인터페이스를 동일하게 변환 

예:) 전기 플러그, 나라마다 서로 다른 플러그의 경우 어댑터를 사용해서 변환시킬 수 있음

 

객체지향 어댑터

- 일상 생활에서 쓰이는 어댑터하고 똑같은 역할을 함

- 어떤 인터페이스를 클라이언트에서 요구하는 형태의 인터페이스에 적응시켜주는 역할을 함

 

가정1.

기존 소프트웨어 시스템에서 새로운 업체에서 제공한 클래스 라이브러리를 사용해야 한다고 가정

문제 상황.

새로 채택한 업체에서 사용하는 인터페이스가 기존 업체에서 사용하던 인터페이스와 다름

해결 방안1.

기존 코드를 바꿀 수 없다면, 업체에서 사용하는 인터페이스를 기존에 사용하던 인터페이스에 적용시켜주는 클래스를 만들어 사용

이 경우 어댑터는 클라이언트로부터 요청을 받아서 업체에서 제공하는 클래스에서 수용할 수 있는 형태로 변환시켜주는 중개인 역할을 함

 

사례1 : 오리 탈을 쓴 칠면조

Duck interface & MallardDuck class(01 디자인 패턴 소개 코드)

public interface Duck {
    public void quack();
    public void fly();
}

public class MallardDuck implements Duck {
    public void quack() {
        System.out.println("Quack");
    }
    public void fly() {
        System.out.println("I'm flying");
    }
}

Turkey interface & WildTurkey class

public interface Turkey {
    public void gobble();
    public void fly();
}

public class WildTurkey implements Turkey {
    public void gobble() {
        System.out.println("Gobble gobble");
    }
    public void fly() {
        System.out.println("I'm flying a short distance");
    }
}

상황1. Duck 객체가 모자라서 터키 객체를 대신 사용해야 하는 상황

어댑터 구현

public class TurkeyAdapter implements Duck {
    Turkey turkey;
    public TurkeyAdapter(Turkey turkey) {
        this.turkey = turkey;
    }
    public void quack() {
        turkey.gobble();
    }
    public void fly() {
        for (int i = 0; i < 5; i++) {
            turkey.fly();
        }
    }
}

 

클래스 다이어그램

public class DuckTestDrive {
  public static void main(String[] args) {
    MallardDuck duck = new MallardDuck();
    WildTurkey turkey = new WildTurkey();
    Duck turkeyAdapter = new TurkeyAdapter(turkey);
    
    System.out.println("The Turkey says…");
    turkey.gobble();
    turkey.fly();
    System.out.println("\nThe Duck says…");
    testDuck(duck);
    System.out.println("\nThe TurkeyAdapter says…");
    testDuck(turkeyAdapter);
  }
  static void testDuck(Duck duck) {
    duck.quack();
    duck.fly();
  }
}

사례2 : Enumeration을 Iterator에 적응시키기

Enumeration 예제 코드

import java.util.*;

public class Enumeration1 {
  public static void printEnumeration(Enumeration e) {
    while (e.hasMoreElements()) {
      System.out.println("" + e.nextElement());
    }
  }    
  public static void main(String[] args) {
    Vector v = new Vector();
    for (int i = 0; i < 10; i++) { 
      v.add(i); 
    }
    Enumeration e = v.elements();        
    Enumeration1.printEnumeration(e);
  }
}

Iterator 예제 코드

import java.util.*;

public class Iterator1 {
  public static void printIterator(Iterator it) {
    while (it.hasNext()) {
      System.out.println("" + it.next());
    }
  }
  public static void main(String[] args) {
    Vector v = new Vector();
    for (int i = 0; i < 10; i++) { 
      v.add(i); 
    }
    Iterator it = v.iterator();
    Iterator1.printIterator(it);
  }
}

Enumeration을 Iterator에 적응시킬 경우 생기는 문제점

코드

public class EnumerationIterator implements Iterator {
  Enumeration enumeration;
  public EnumerationIterator(Enumeration enmt) {
    this.enumeration = enmt;
  }
  public boolean hasNext() {
    return enumeration.hasMoreElements();
  }
  public Object next() {
    return enumeration.nextElement();
  }
  public void remove() {
    throw new UnsupportedOperationException(); // 예외 처리
  }
}
public class Iterator2 {
  public static void printIterator(Iterator it) {
    while (it.hasNext()) {
      System.out.println("" + it.next());
    }
  }
    
  public static void main(String[] args) {
    Vector v = new Vector();
    for (int i = 0; i < 10; i++) { 
      v.add(i); 
    }
    Enumeration e = v.elements();
    EnumerationIterator it = new EnumerationIterator(e);
    Iterator2.printIterator(it);
  }
}

어댑터 사용 예

자바에서 배열을 고정 크기의 리스트로 변환

- Arrays.asList() 함수 사용

- 변환된 리스트는 ArraysAdapter로서 Arrays의 특징을 가지게 됨

1. 리스트로 변환하더라도 고정 크기이므로 add(), remove() 사용 불가

2. 리스트의 set() 함수를 이용해서 요소 내용 변경 가능(단, 원래 배열의 내용도 변경됨)

 

코드

import java.util.Arrays;
import java.util.List;

public class ArraysAdapter {
  public static void main(String[] args) {
    String[] cities = { "Seoul", "Incheon", "Busan", "Sejong" };
    List<String> cityList = Arrays.asList(cities);
    
    System.out.printf("cities.length = %d\n", cities.length);
    System.out.printf("cityList.size = %d\n", cityList.size());
    
    cityList.set(0, "Suwon");       
    System.out.println("\nPrint out cities");
    
    for (String s : cities) {
      System.out.println(s);
    }
    System.out.println("\nPrint out cityList");
    
    for (String s : cityList) {
      System.out.println(s);
    }   
  }
} 

07-02. Facade Pattern

목적

서브시스템에 있는 여러 개의 인터페이스를 통합하는 한 개의 인터페이스를 제공.

퍼사드는 서브 시스템을 쉽게 사용할 수 있도록 해주는 고급 수준의 인터페이스를 정의

 

요소

설명

이름

퍼사드(Facade)

문제

서브시스템이 너무 많고 사용하기가 복잡함

해결방안

단순한 인터페이스를 제공하는 객체를 중간에 넣음

결과

최소 지식 원칙에 입각해 의존성 최소화

 

퍼사드 패턴의 정의

홈 씨어터

홈 씨어터 구축 가정

- 디비디 플레이어, 프로젝터, 자동 스크린, 서라운드 음향 시스템 및 팝콘 기계를 갖춘 시스템을 구성

영화 보기(복잡한 방법)

- 팝콘 기계를 켜고 튀기기 시작

- 전등을 어둡게 조절, 스크린을 내림

- 프로젝트를 켜고 프로젝터로 디비디 신호 입력

- 프로젝터를 와이드 스크린 모드로 전환

- 엠프를 켜고 디비디로 전환

- 엠프를 서라운드 음향 모드로 전환

- 엠프 볼륨을 중간(5)로 설정

- 디비디 플레이어를 켜고 재생 시작

 

클래스 다이어그램

영화 보기를 코드로 구현

popper.on();
popper.pop();
lights.dim(10);
screen.down();
projector.on();
projector.setInput(dvd);
projector.wideScreenMode();
amp.on();
amp.setDvd(dvd);
amp.setSurroundSound();
amp.setVolume(5);
dvd.on();
dvd.play(movie);

 

퍼사드 패턴을 이용한 홈 씨어터 구현

- 퍼사드 패턴을 이용해서 쓰기 쉬운 인터페이스를 제공

- 복잡한 시스템을 직접 건드리고 싶다면 기존 인터페이스 사용 가능

- 퍼사드 클래스 제공

- 홈 씨어터 시스템 용 퍼사드 클래스 제공

- 퍼사드 클래스에서는 홈 씨어터 구성요소들을 하나의 서브 시스템으로 간주하고, watchMovie() 메소드에서는 서브시스템의 메소드들을 호출하여 필요한 작업을 처리

- 클라이언트는 서브 시스템이 아닌 홈 씨어터 퍼사드에 있는 메소드를 호출

- 퍼사드를 쓰더라도 서브시스템에는 여전히 직접 접근 가능

코드

public class HomeTheaterFacade {
  private Amplifier amp;
  private Tuner tuner;
  private DvdPlayer dvd;
  private CdPlayer cd;
  private Projector projector;
  private TheaterLights lights;
  private Screen screen;
  private PopcornPopper popper;
 
  public HomeTheaterFacade(Amplifier a, Tuner t, 
      DvdPlayer d, CdPlayer c, Projector p, 
      Screen s, TheaterLights l, PopcornPopper pp) {
    this.amp = a; this.tuner = t; this.dvd = d;  
    this.cd = c; this.projector = p; this.lights = l; 
    this.screen = s; this.popper = pp;
  }
  public void watchMovie(String movie) {
    System.out.println("Get ready to watch a movie...");
    popper.on();
    popper.pop();
    lights.dim(10);
    screen.down();
    projector.on();
    projector.wideScreenMode();
    amp.on();
    amp.setDvd(dvd);
    amp.setVolume(5);
    dvd.on();
    dvd.play(movie);
  }
    public void endMovie() {
        System.out.println("Shutting movie theater down...");
        projector.off();
        amp.off();
        dvd.stop();
        dvd.eject();
        dvd.off();
    }
    public void listenToRadio(double frequency) {
        System.out.println("Tuning in the airwaves...");
        tuner.on();
        tuner.setFrequency(frequency);
        amp.on();
        amp.setVolume(5);
        amp.setTuner(tuner);
    }
    public void endRadio() {
        System.out.println("Shutting down the tuner...");
        tuner.off();
        amp.off();
    }
}
  public void endMovie() {
    System.out.println("Shutting movie theater down..");
    popper.off();
    lights.on();
    screen.up();
    projector.off();
    amp.off();
    dvd.stop();
    dvd.eject();
    dvd.off();
  }
  … // 기타 메소드
}


퍼사드 패턴 사용

public class HomeTheaterTestDrive {
  public static void main(String[] args) {
    // 컴포넌트 객체 생성 코드
    HomeTheaterFacade homeTheater 
     = new HomeTheaterFacade(amp, tuner, dvd, cd, 
              projector, screen, lights, popper);
    homeTheater.watchMovie("Raiders of the Lost Ark");
    homeTheater.endMovie();
  }
}

 

 

 

 

 

 

반응형