BackEnd/Design Patterens

[Design Patterns] Facade Pattern : 퍼사드 패턴

샤아이인 2022. 1. 13.

Head First Design Patterns 책을 읽으며 정리한 내용입니다. 문제가 될 시 글을 내리도록 하겠습니다!

Facade Pattern 이란?

 

Facade Pattern - 어떤 서브시스템의 일련의 인터페이스에 대한 통합된 인터페이스를 제공합니다.
퍼사드에서 고수준 인터페이스를 정의하기때문에 서브시스템을 더 쉽게 사용할 수 있습니다.

 

퍼사드 패턴에서는 인터페이스를 단순화 시키기위해 인터페이스를 변경합니다! 하나 이상의 복잡한 인터페이스를 깔끔하게 퍼사드 로 덮어주거든요! 더 나아가 쉽게 사용할수 있도록 도와준답니다. 다음 다이어그램을 함께 확인해 보시죠!

클라이언트가 퍼사드 덕분에 정말 편하게 서브시스템들을 사용하고 있군요!


패턴 소개

다음과 같이 클래스들이 서로 복잡하게 얽혀있고, 사용할려면 수많은 인터페이스를 사용해야하는 영화관람 시스템이 있군요!

우선 얼마나 엉망으로 되있는지 확인해 봅시다 ~~

출처 -  http://blog.lukaszewski.it/2013/08/31/design-patterns-facade/
전선과 프로젝터를 설치하고, 장비들 간에 케이블로 연결해주고, 세심한 부분들을 모두 조율해주고 나니 드디어! 영화를 볼 수 있게 되었습니다. 서로 너무 엉켜 있는것 같군요!

이제 준비가 된 것 같으니 영화를 봅시다!

 

아? 근데 영화를 볼려면 아직 조금더 수고해야 하는군요...

 

1) 콜라꺼내기

2) 팝콘 튀기기

3) 스크린 내리기

4) 프로젝터 켜기

5) 프로젝터 와이드 모드로 해상도 맞추기

6) 앰프 켜기

7) DVD 모드 전환

8) 볼륨 설정

9) DVD 플레이어 켜기

10) DVD 재생

 

영화 하나 재생하는데 한 3시간은 걸릴것 같군요! 이대로 뒀다가는 답도 없을 것 같습니다...

 

이제 영화를 보기만 하면 끝날 것 같죠? 아직 끝난게 아닙니다...

1) 끝난 영화는 어떻게 끄죠? 아마 위에서 했던걸 다시 역순으로... (생각만 해도 토가 나ㅇㅗㅂ....우엑)

2) 시스템을 업그레이드 하면 또 작동방법을 배워야하지 않을까요?

 

퍼사드 패턴을 통하여 이렇게 복잡한 일을 간단하게 처리하고 영화를 즐겨봅시다!


퍼사드가 어떤 식으로 작용할지 살펴봅시다~

 

1) 영화보기 시스템용 퍼사드를 만들면, watchMovie() 같이 간단한 메소드만 들어있는 HomeTheateFacade라는 클래스를 새롭게 만들어야 합니다.

 

2) 퍼사드 클래스에서는 영화보기 구성요소들을 하나의 서브시스템으로 간주하고, watchMovie() 메소드에서는 필요한 서브시스템의 메소드들을 관리하면서 필요할때 호출하면서 작업을 처리합니다.

 

3) 클라이언트 에서는 서브시스템들을 직접 호출하는 것 이 아니라, watchMovie()만 호출하면 알아서 전등, DVD, 팝콘, 콜라, 음향조절 등 모든 준비가 한번의 클릭으로 끝날 수 있게됩니다. 추가적으로 어떤 순서로 함수들이 호출되야하는지도 알고 있으니 똑똑하다 할 수 있겠군요!

 

4) 퍼사드를 사용하더라도 여전히 직접 접근이 가능합니다. 서브시스템의 고급 기능이 필요하다면 언제든지 쓸 수 있습니다!

주의하세요! 서브시스템 클래스들을 캡슐화 하는 것 이 아닙니다!, 그냥 서브시스템들을 사용할 간단한 인터페이스를 제공할 뿐 입니다!

 

다음과 같이 말이죠!

출처 -  http://blog.lukaszewski.it/2013/08/31/design-patterns-facade/
 

여기서 궁금한점이 한가지 생겼습니다. 그럼 어댑터는 한 클래스만 감싸고퍼사드는 여러 클래스를 감싸는 것 인가요?

정답부터 말하며 NO 입니다.

 

대부분의 디자인패턴 책에서 한 클래스에 대해서 어댑터를 만드는 예만 있어서 그렇게 생각하실수도 있겠습니다만, 클라이언트가 필요한 인터페이스에 맞추기 위해 여러 클래스를 사용해도 어댑터패턴 이라 할 수 있습니다.

 

퍼사드도 꼭 여러 클래스를 감싸야만 하는 건 아닙니다. 아주 복잡한 인터페이스를 가지고 있는 단 하나의 클래스에 대해서 퍼사드를 만들수도 있습니다.

 

어댑터와 퍼사드의 차이점은 감싸는 클래스의 개수가 아니라, 그 용도에 있습니다.

 

어댑터는 인터페이스를 변경해서 클라이언트가 필요로 하는 인터페이스로 적응시키기 위한 용도 입니다.

퍼사드는 어떤 서브시스템에 대한 간단한 인터페이스를 제공하기 위한 용도로 사용되죠.


지금부터는 코드로 살펴봅시다! 우선 HomeTheaterFacade 클래스 부터 살펴봅시다!

public class HomeTheaterFacade {
	Amplifier amp; // 서브시스템들의 참조변수들
	Tuner tuner;
	DvdPlayer dvd;
	CdPlayer cd;
	Projector projector;
	TheaterLights lights;
	Screen screen;
	PopcornPopper popper;
 
	public HomeTheaterFacade(Amplifier amp, // 생성자에서 레퍼런스를 다 받아옴
				 Tuner tuner, 
				 DvdPlayer dvd, 
				 CdPlayer cd, 
				 Projector projector, 
				 Screen screen,
				 TheaterLights lights,
				 PopcornPopper popper) {
 
		this.amp = amp;
		this.tuner = tuner;
		this.dvd = dvd;
		this.cd = cd;
		this.projector = projector;
		this.screen = screen;
		this.lights = lights;
		this.popper = popper;
	}
 
	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.setSurroundSound();
		amp.setVolume(5);
		dvd.on();
		dvd.play(movie);
	}
 
 
	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 void listenToCd(String cdTitle) {
		System.out.println("Get ready for an audiopile experence...");
		lights.on();
		amp.on();
		amp.setVolume(5);
		amp.setCd(cd);
		amp.setStereoSound();
		cd.on();
		cd.play(cdTitle);
	}

	public void endCd() {
		System.out.println("Shutting down CD...");
		amp.off();
		amp.setCd(cd);
		cd.eject();
		cd.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();
	}
}
 

퍼사드의 생성자에는 각 서브시스템의 구성요소의 레퍼런스가 전달됩니다.

 

또한 영화를 보기위해 watchMovie() 를 호출하면 이전에는 일일이 수동으로 했던 작업들을 순서대로 처리하게 됩니다. 복잡했던 일들을 하나의 메소드로 간단하게 처리가 가능해진 것 입니다.

 

이제 정상적으로 작동하는지 확인해봐야겠죠? 다음 테스트 코드를 실행해 봅시다!

public class HomeTheaterTestDrive {
    public static void main(String[] args) {
        Amplifier amp = new Amplifier("Top-O-Line Amplifier");
        Tuner tuner = new Tuner("Top-O-Line AM/FM Tuner", amp);
        DvdPlayer dvd = new DvdPlayer("Top-O-Line DVD Player", amp);
        CdPlayer cd = new CdPlayer("Top-O-Line CD Player", amp);
        Projector projector = new Projector("Top-O-Line Projector", dvd);
        TheaterLights lights = new TheaterLights("Theater Ceiling Lights");
        Screen screen = new Screen("Theater Screen");
        PopcornPopper popper = new PopcornPopper("Popcorn Popper");

        HomeTheaterFacade homeTheater = new HomeTheaterFacade(amp, tuner, dvd, cd,
                        projector, screen, lights, popper);

        homeTheater.watchMovie("Raiders of the Lost Ark"); // 단순화된 인터페이스를 써서 영화 재생을 시작
        homeTheater.endMovie();
    }
}
 

위 코드의 실행결과는 다음과 같습니다.


여기서 잠깐!!

최소 지식 원칙 에 대하여 알아봅시다.

최소 지식 원칙이란 객체 사이의 상호작용은 될 수 있으면 아주 가까운 "친구" 사이에서만 허용하라 는 원칙입니다.

 

정말 친한 친구하고만 얘기하라.


그런데 어떻게 해야 여러 객체하고 인연을 맺는 것을 피할 수 있을까요? 일종의 가이드 라인을 제시해보면,

어떤 메소드에서든지 다음 4종류의 객체의 메소드만을 호출하면 됩니다.

 

▶ 객체 자체

▶ 메소드에 매개변수로 전달된 객체

▶ 그 메소드에서 생성하거나 인스턴스를 만든 객체

▶ 그 객체에 속하는 구성요소(인스턴스 변수에 의해 참조되는 객체)

 

다음 코드를 통해 확인하면 더 명확해지실 거에요!

public class Car{
    Engine engine; // 이 클래스의 구성요소, 이 구성요소의 메소드는 호출해도 됩니다!
    
    public Car() {}
    
    public void start(Key key){ // 매개변수로 전달된 객체의 메소드는 호출해도 됩니다!
        Doors doors = new Doors(); // 새로생성한 객체의 메소드는 호출해도 됩니다!
        
        boolean authorized = key.turns(); // 매개변수 key로 전달된 객체의 메소드는 호출해도 됩니다!
        
        if(authorized){
            engine.start(); // 이 클래스의 구성요소의 메소드는 호출해도 됩니다!
            updateDashboardDisplay(); // 클래스 내에 있는 메소드는 호출해도 됩니다!
            doors.lock(); // 새로생성한 객체의 메소드는 호출해도 됩니다!
        }
    }
    
    public void updateDashboardDisplay(){
        // ...
    }
}

댓글