어댑터 패턴(Adapter Pattern)
정의
한 클래스의 인터페이스를 클라이언트에서 사용하고자 하는 다른 인터페이스로 변환합니다. 어댑터를 이용하면 인터페이스 호환성 문제 때문에 같이 쓸 수 없는 클래스들을 연결해서 쓸 수 있습니다.
- 이 패턴을 이용하면 호환되지 않는 인터페이스를 사용하는 클라이언트를 그대로 활용할 수 있습니다.
- 어댑티를 새로 바뀐 인터페이스로 감쌀 때는 객체 구성(Composition)을 사용합니다.
- 클라이언트를 특정 구현이 아닌 인터페이스에 연결 시킵니다.
// Target Interface public interface Duck { public void quack(); public void fly(); } public class DuckAdapter implements Turkey { Duck duck; public DuckAdapter(Duck duck) { this.duck = duck; } public void gobble() { duck.quack(); } public void fly() { duck.fly(); } } // Adaptee Interface public interface Turkey { public void gobble(); public void fly(); } public class WildTurky implements Turkey { public void gobble() { System.out.println("Gobble gobble"); } public void fly() { System.out.println("I'm flying a short distnace"); } } // Adapter에서는 Target Interface를 구현하고, Adapter는 Adaptee Interface로 구성되어 있습니다. // 모든 요청은 Adaptee 에게 위임 됩니다. 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(); } } } // Client 에서는 target Interface 만 볼 수 있습니다. public class Main { public static void main(String[] args) { MallardDuck duck = new MallardDuck(); WildTurky turkey = new WildTurky(); 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(); } }
퍼사드 패턴(Facade Pattern)
정의
어떤 서브시스템의 일련의 인터페이스에 대한 통합된 인터페이스를 제공합니다. 퍼사드에서 고수준 인터페이스를 정의하기 때문에 서브시스템을 더 쉽게 사용할 수 있습니다.
public class Facade { // 우리가 사용하고자 하는 서브시스템의 모든 구성요소들이 인스턴스 변수 형태로 저장 됩니다. DvdPlayer dvd; Light light; Screen screen; public Facade(DvdPlayer dvd, Light light, Screen screen) { this.dvd = dvd; this.light = light; this.screen = screen; } // 하나하나 수동으로 작업 했던 내용들을 하나의 메소드로 순서대로 처리 합니다. // 각 작업은 서브시스템에 들어있는 구성요소들에게 위임 됩니다. public watchMove(String movie) { dvd.on(); light.on(); dvd.open(); dvd.insert(); screen.on(); dvd.play(); } }
객체지향 원칙(디자인 원칙)
1. 애플리케이션에서 바뀌는 부분을 찾아내서 바뀌지 않는 부분으로부터 분리 시켜 캡슐화 한다.
2. 상속보다는 구성을 활용한다.
3. 구현이 아닌 인터페이스에 맞춰서 프로그래밍 한다.
4. 서로 상호작용을 하는 객체 사이에서는 가능하면 느슨하게 결합하는 디자인을 사용해야 한다.
5. 클래스는 확장에 대해서는 열려 있지만 변경에 대해서는 닫혀 있어야 한다.(OCP : Open-Closed Principle)
6. 추상화된 것에 의존하도록 만들어라. 구상 클래스에 의존하도록 만들지 않도록 한다. (의존성 뒤집기 윈칙:Dependency Inversion Principle)
7. 최소 지식 원칙(Principle of Least Knowledge) - 정말 친한 친구하고만 얘기하라.
- 시스템을 디자인할 때, 어떤 객체든 그 객체와 상호작용을 하는 클래스의 개수에 주의해야 하며, 그런 객체들과 어떤 식으로 상호작용을 하는지에도 주의를 기울여야 한다.
- 최소 지식 원칙 단점 : 이 원칙을 적용하다 보면 다른 구성요소에 대한 메소드 호출을 처리하기 위해 "래퍼" 클래스를 더 만들어야 할 수도 있습니다. 그러다 보면 시스템이 더 복잡해지고, 개발 시간도 늘어나고, 성능이 떨어질 수도 있습니다.
7번 원칙에 대한 가이드 라인을 제시 합니다. 어떤 메소드에서든지 다음 네 종류의 객체의 메소드만을 호출 하면 됩니다.
1. 객체 자체
2. 메소드에 매개변수로 전달된 객체
3. 그 메소드에서 생성하거나 인스턴스를 만든 객체
4. 그 객체에 속하는 구성요소
예제)
- 원칙을 따르지 않은 경우
// station으로부터 thermometer라는 객체를 받은 다음, 그객체의 // getTemperature() 메소드를 직접 호출 합니다. public float getTemp() { Thermometer thermometer = station.getThermometer(); return thermometer.getTemperature(); }
- 원칙을 따르는 경우
// 최소 지식 원칙을 적용하여 Station 클래스에 thermometer에 요청을 해 주는 // 메소드를 추가했습니다. 이렇게 하면 의존해야 하는 클래스의 개수를 줄일 수 있죠. public float getTemp() { return station.getTemperature(); }
최소 지식 원칙을 따르면서 메소드를 호출하는 방법 예제
public class Car { // 클래스의 구성요서. 이 구성요소의 메소드는 호출해도 되죠. Engine engine; // 기타 인스턴스 변수 public Car() { // 엔진 초기화 등을 처리 } public void start(Key key) { // 새로운 객체를 생성 합니다. 이 객체의 메소드는 호출해도 됩니다. Doors doors = new Doors(); // 매개변수로 전달된 객체의 메소드는 호출해도 됩니다. boolean authorized = key.turns(); if (authorized) { // 이 객체의 구성요소의 메소드는 호출해도 되죠? engine.start(); // 객체 내에 있는 메소드는 호출해도 됩니다. updateDashboardDisplay(); // 직접 생성하거나 인스턴스를 만든 객체의 메소드는 호출해도 됩니다. doors.lock(); } } public void updateDashboardDisplay() { // 디스플레이 갱신 } }
어댑터 패턴과 퍼사드 패턴의 차이점
어댑터 패턴은 인터페이스를 변경해서 클라이언트에서 필요로 하는 인터페이스로 적응시키기 위한 용도
퍼사드 패턴은 어떤 서브시스템에 대한 간단한 인터페이스를 제공하기 위한 용도
퍼사드는 인터페이스를 단순화 시키기 위한 용도로 쓰이는 반면, 어댑터는 인터페이스를 다른 인터페이스로 변환하기 위한 용도로 쓰입니다.
핵심 정리
- 기존 클래스를 사용하려고 하는데 인터페이스가 맞지 않으면 어댑터를 쓰면 됩니다.
- 큰 인터페이스, 또는 여러 인터페이스를 단순화시키거나 통합 시켜야 되는 경우에는 퍼사드를 쓰면 됩니다.
- 어댑터는 인터페이스를 클라이언트에서 원하는 인터페이스로 바꿔주는 역할을 합니다.
- 퍼사드는 클라이언트를 복잡한 서브시스템과 분리시켜주는 역할을 합니다.
- 어댑터를 구현할 때는 타겟 인터페이스의 크기와 구조에 따라 코딩해야 할 분량이 결정됩니다.
- 퍼사드 패턴에서는 서브시스템을 가지고 퍼사드를 만들고, 실제 작업은 서브클래스에 맡깁니다.
- 어댑터 패턴에는 객체 어댑터 패턴과 클래스 어댑터 패턴이 있다. 클래스 어댑터를 쓸려면 다중 상속 기능이 필요 합니다.
- 한 서브시트템에 퍼사드를 여러개 만들어도 됩니다.
- 어댑터는 객체를 감싸서 인터페이스를 바꾸기 위한 용도로, 데코레이터는 객체를 감싸서 새로운 행동을 추가하기 위한 용도로, 퍼사드는 일련의 객체들을 감싸서 단순화 시키기 위한 용도로 쓰입니다.
'Programs > Design Pattern' 카테고리의 다른 글
디자인 패턴(Design Pattern) - Iterator Pattern, Composite Pattern (2) | 2011.10.18 |
---|---|
디자인 패턴(Design Pattern) - Template Method Pattern (0) | 2011.10.18 |
디자인 패턴(Design Pattern) - Command Pattern (2) | 2011.10.04 |
디자인 패턴(Design Pattern) - Singleton Pattern (1) | 2011.09.27 |
디자인 패턴(Design Pattern) - Factory Pattern (6) | 2011.09.23 |