2016년 3월 8일 화요일

Adapter 패턴 vs. Facade 패턴 vs. Decorator 패턴

1) 어댑터 패턴


1-1. 정의

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

1-2. 클래스 어댑터 패턴

단점: 상속(Generalzation)을 활용하기때문에 유연하지 못하다.
장점: 어댑터(Adapter)를 전체를 다시 구현할 필요가 없다.(빠르다)


주의: 클래스 어댑터에서는 다중상속을 이용하기 때문에 자바에서는 쓸 수 없습니다.
쓸수있습니다.(간접적 다중상속 지원)




- 코드


public class Adaptee {
public void specificRequest() {
System.out.println("Adaptee.speificRequest()");
}
}

public interface Target {
public void request();
}

public class Adapter extends Adaptee implements Target {
public void request() {
this.specificRequest(); // 다중상속
}
}

public class Client {
public static void main(String[] args) {
Target target = new Adapter();

target.request();
}
}

- 결과 : "Adaptee.speificRequest()" 출력

1-3. 객체 어댑터 패턴

단점: 어댑터(Adapter)클래스의 대부분의 코드를 구현해야하기때문에 효율적이지 못하다
장점: 구성(Composition)을 사용하기 때문에 더 뛰어나다.(유연하다)


- 코드


public class Adaptee {
public void specificRequest() {
System.out.println("Adaptee.speificRequest()");
}
}

public interface Target {
public void request();
}

public class Adapter implements Target {
Adaptee adaptee = new Adaptee(); // 객체를 만들고

public void request() {
adaptee.specificRequest(); // 객체를 연결한다.
}
}

public class Client {
public static void main(String[] args) {
Target target = new Adapter();

target.request();
}
}



- 결과 : "Adaptee.speificRequest()" 출력


2) 퍼사드 패턴


2-1. 정의

퍼사드는 클래스 라이브러리 같은 어떤 소프트웨어의 다른 커다란 코드 부분에 대한 간략화된 인터페이스를 제공하는 객체이다.

- 퍼사드는 소프트웨어 라이브러리를 쉽게 사용할 수 있게 해준다. 또한 퍼사드는 소프트웨어 라이브러리를 쉽게 이해할 수 있게 해 준다. 퍼사드는 공통적인 작업에 대해 간편한 메소드들을 제공해준다.
- 퍼사드는 라이브러리를 사용하는 코드들을 좀 더 읽기 쉽게 해준다.
- 퍼사드는 라이브러리 바깥쪽의 코드가 라이브러리의 안쪽 코드에 의존하는 일을 감소시켜준다. 대부분의 바깥쪽의 코드가 퍼사드를 이용하기 때문에 시스템을 개발하는 데 있어 유연성이 향상된다.
- 퍼사드는 좋게 작성되지 않은 API의 집합을 하나의 좋게 작성된 API로 감싸준다.

래퍼(wrapper)가 특정 인터페이스를 준수해야 하며, 폴리모픽 기능을 지원해야 할 경우에는 어댑터 패턴을 쓴다. 단지, 쉽고 단순한 인터페이스를 이용하고 싶을 경우에는 퍼사드를 쓴다.


- 퍼사드
퍼사드 클래스는 패키지 1,2,3 및 그림에 나오지 않은 그 밖의 응용 프로그램 코드와 상호 동작한다.
- 클라이언트
패키지 내의 리소스들을 접근하기 위해 퍼사드 클래스를 쓰는 객체들이다.
- 패키지
소프트웨어 라이브러리 / API 집합이다. 퍼사드 클래스를 통해 접근된다.



/* Complex parts */

class CPU {
public void freeze() { ... }
public void jump(long position) { ... }
public void execute() { ... }
}

class Memory {
public void load(long position, byte[] data) {
...
}
}

class HardDrive {
public byte[] read(long lba, int size) {
...
}
}

/* Fa?ade */

class Computer {
public void startComputer() {
CPU cpu = new CPU();
Memory memory = new Memory();
HardDrive hardDrive = new HardDreive();
cpu.freeze();
memory.load(BOOT_ADDRESS, hardDrive.read(BOOT_SECTOR, SECTOR_SIZE));
cpu.jump(BOOT_ADDRESS);
cpu.execute();
}
}

/* Client */

class You {
public static void main(String[] args) throws ParseException {
Computer facade = /* grab a facade instance */;
facade.startComputer();
}
}


3) Adapter 패턴 vs. Facade 패턴 vs. Decorator 패턴 차이점


각 패턴의 용도들을 비교해 차이점을 알아보자 !

* Adapter 패턴 : 한 인터페이스를 다른 인터페이스로 변환 (for 호환성)
인터페이스를 변경해서 클라이언트에서 필요로 하는 인터페이스로 적응시키기 위한 용도.

* Facade 패턴 : 인터페이스를 간단하게 바꿈 (for 간편함)
어떤 서브시스템에 대한 간단한 인터페이스를 제공하기 위한 용도.

* Decorator 패턴 : 인터페이스를 바꾸지 않고 책임(기능)만 추가함 (for 기능 추가)

객체를 감싸서 새로운 행동을 추가하기 위한 용도.

4) 최소 지식 원칙 (Principle of Least Knowledge) 또는 데메테르의 법칙 (Law of Demeter)

- 참고로 둘 다 같은 말이다.
- 정말 친한 친구들하고만 얘기하라.
- 최소 지식 원칙에 따르면 객체 사이의 상호작용은 될 수 있으면 아주 가까운 '친구' 사이에서만 허용하는 것이 좋다 !
- 이를 잘 따르면 여러 클래스들이 복잡하게 얽힌 시스템의 한 부분을 변경했을 때, 다른 부분까지
줄줄이 고쳐야 되는 상황을 미리 방지할 수 있다.

- 이 원칙을 지키기 위한 가이드 라인 : 다음 4 종류의 객체의 메소드만 호출하면 된다.
(1) 객체 자체
(2) 메소드에 매개변수로 전달된 객체
(3) 그 메소드에서 생성하거나 인스턴스를 만든 객체
(4) 그 객체에 속하는 구성요소
구성요소는 인스턴스 변수에 의해 참조되는 객체를 의미. 즉, 'A에는 B가 있다' 관계에 있는 객체.

- 위 가이드 라인에 따르면 다른 메소드를 호출해서 리턴 받은 객체의 메소드를 호출하는 건 바람직하지 않다고 한다.

- 객체들 사이의 의존성을 낮추고, 소프트웨어 관리가 더 용이해질 수는 있으나 단점도 있다.
다른 구성요소에 대한 메소드 호출을 처리하기 위해 'Wrapper' 클래스를 더 만들어야 할 수도 있다.
그러다 보면 시스템이 더 복잡해지면서 개발 시간이 늘어나고 성능 저하를 불러일으키는 요인이 될 것이다.

5) 객체지향의 원칙 

1. 바뀌는 부분은 캡슐화 한다.
2. 상속보다는 구성을 활용한다.
3. 구현이 아닌 인터페이스에 맞춰서 프로그래밍 한다.
4. 서로 상호작용을 하는 객체 사이에서는 가능하면 느슨하게 결합하는 디자인을 사용해야 한다.
5. 클래스는 확장에 대해서는 열려 있어야 하지만 코드 변경에 대해서는 닫혀 있어야 한다(OCP).
6. 추상화된 것에 의존해라. 구상 클래스에 의존해서는 안 된다(의존성 뒤집기 법칙).
7. 친한 친구들하고만 이야기한다(최소 지식 원칙).


6) 총 정리 

- 기존 클래스를 사용하려고 하는데 인터페이스가 맞지 않으면 어댑터를 쓰자.
- 큰 인터페이스 or 여러 인터페이스를 단순화 시키거나 통합시켜야 되는 경우에는 퍼사드를 쓰자.

- 어댑터는 인터페이스를 클라이언트가 원하는 인터페이스로 바꿔주는 역할을 한다.
- 퍼사드는 클라이언트를 복잡한 서브시스템과 분리시켜주는 역할을 한다.

- 어댑터를 구현할 때는 Target 인터페이스의 크기와 구조에 따라 코딩해야 할 분량이 결정된다.
- 퍼사드 패턴에서는 서브시스템을 가지고 퍼사드를 만들고, 실제 작업은 서브클래스에 맡긴다.

- 어댑터 패턴에서는 객체 어댑터 패턴 vs. 클래스 어댑터 패턴(다중상속必)이 있다.
- 어댑터는 다중 어댑터로 만들 수 있고, 퍼사드는 한 서브시스템에 여러 개 만들어도 된다.

- 어댑터, 퍼사드 모두 인터페이스를 바꿔주는데 !
Adapter : 인터페이스를 변환
Facade : 인터페이스를 통합/단순화 시킴
Decorator : 인터페이스를 바꾸지 않고, 객체를 감싸서 새로운 기능을 추가할 수 있다.


*reference

  • http://wiki.gurubee.net/pages/viewpage.action?pageId=1507407#7.%EC%96%B4%EB%8C%91%ED%84%B0%ED%8C%A8%ED%84%B4%EA%B3%BC%ED%8D%BC%EC%82%AC%EB%93%9C%ED%8C%A8%ED%84%B4-33.%EC%98%88%EC%A0%9C%3AXML%ED%8A%B8%EB%A6%AC
  • http://secretroute.tistory.com/entry/Head-First-Design-Patterns-%EC%A0%9C7%EA%B0%95-Adapter-%ED%8C%A8%ED%84%B4%EA%B3%BC-Facade-%ED%8C%A8%ED%84%B4
  • https://ko.wikipedia.org/wiki/%ED%8D%BC%EC%82%AC%EB%93%9C_%ED%8C%A8%ED%84%B4


Share:

0 개의 댓글:

댓글 쓰기