카샤의 만개시기

Facade Pattern 본문

Foundation/Design Pattern

Facade Pattern

SKaSha 2019. 10. 19. 00:31

GoF 책에서 퍼사드 패턴에 대하여 다음과 같이 소개합니다.

하위 시스템의 인터페이스 세트에 일관된 인터페이스를 제공하는 것

또한 헤드 퍼스트 디자인 책에서는 다음과 같이 소개합니다.

퍼사드 패턴은 하위 시스템의 복잡도를 감추는 동시에 그 전체 기능을 사용하기 쉬운 인터페이스로 제공한다

이해를 돕기 위해 예제를 통해 알아보자.

나는 오늘 영화관에서 32만원의 수익을 낸 직원에게서, 
좌석이 100여 좌석이 있고 가격은 10000원인 '어벤져스' 영화 티켓을 구입하여 
상영관에 입장하여 영화를 보았다.

마치 고등학생 때 풀던 영어 지문처럼 필요 이상의 내용들이 들어가 있다.
우리가 알고자 하는건 간단한다.

나는 '어벤져스' 영화를 보았다.

퍼사드 패턴은 위의 지문을 아래 지문처럼 만드는것과 같다.
영화를 보는 행동에 필요한 하위 인터페이스들을 감춤으로써, 시스템의 복잡도를 감추고 캡슐화를 하는것과 같다.

레거시 코드

영화관

@Getter
class Cinema {
    private Seller seller;
    private Set<Movie> movieSet;

    Cinema() {
        seller = new Seller();
        movieSet = Stream.of(
                        new Movie("어벤져스", 10000, 10),
                        new Movie("조커", 9000, 10)
                    ).collect(Collectors.toCollection(HashSet::new));
    }

    void enterTheater(Client client) throws Exception {
        if(!client.isHasTicket()) {
            throw new Exception();
        }

        System.out.println("영화관 입장");
    }
}

고객

@Data
class Client {
    @NonNull private int money;
    private boolean hasTicket = false;
}

영화

@Data
class Movie {
    @NonNull private String title;
    @NonNull private int price;
    @NonNull private int ticket;
}

판매원

class Seller {
    private int income = 0;

    void sellTicket(Client client, Movie movie) {
        int price = movie.getPrice();
        client.setMoney(client.getMoney() - price);
        income += price;

        client.setHasTicket(true);
        movie.setTicket(movie.getTicket() - 1);
    }
}

실행

public class FacadeLegacyTest {
    private Cinema cinema;
    private Client client;

    private static final String MOVIE_NAME = "어벤져스";

    @Before
    public void setUp() {
        cinema = new Cinema();
        client = new Client(50000);
    }

    @Test
    public void test() throws Exception {
        Seller seller = cinema.getSeller();
        Optional<Movie> movieOptional = cinema.getMovieSet().stream().filter(e -> e.getTitle().equals(MOVIE_NAME)).findAny();

        if(movieOptional.isEmpty() || movieOptional.get().getPrice() > client.getMoney()) {
            throw new Exception();
        }

        Movie movie = movieOptional.get();

        seller.sellTicket(client, movie);
        cinema.enterTheater(client);
    }
}

test 함수를 보자.
우리는 그저 '어벤져스'라는 영화를 보았다는 사실을 알고 싶을뿐이다.
하지만 어벤져스라는 영화가 존재하는지, 돈이 있는지 확인하고 직원에게서 티켓을 사는 절차까지 모두 나와있다.
그렇다면 실행클래스는 Cinema, Seller, Movie, Client 모두에게 의존성을 갖게 되는것이고 이는 강한 결합도를 갖게 된다.

코드 개선

영화관

class Cinema {
    private Seller seller;
    private Set<Movie> movieSet;

    Cinema() {
        seller = new Seller();
        movieSet = Stream.of(
                        new Movie("어벤져스", 10000, 10),
                        new Movie("조커", 9000, 10)
                    ).collect(Collectors.toCollection(HashSet::new));
    }

    private Optional<Movie> selectMovie(String title) {
        return movieSet.stream().filter(e -> e.getTitle().equals(title)).findAny();
    }

    void enterTheater(Client client, String title) throws Exception {
        Optional<Movie> movieOptional = selectMovie(title);

        if(movieOptional.isEmpty() || movieOptional.get().getPrice() > client.getMoney()) {
            throw new Exception();
        }

        seller.sellTicket(client, movieOptional.get());

        if(!client.isHasTicket()) {
            throw new Exception();
        }

        System.out.println("영화관 입장");
    }
}

고객

@Data
class Client {
    @NonNull private int money;
    private boolean hasTicket = false;
}

영화

@Data
class Movie {
    @NonNull private String title;
    @NonNull private int price;
    @NonNull private int ticket;
}

판매원

class Seller {
    private int income = 0;

    void sellTicket(Client client, Movie movie) {
        int price = movie.getPrice();
        client.setMoney(client.getMoney() - price);
        income += price;

        client.setHasTicket(true);
        movie.setTicket(movie.getTicket() - 1);
    }
}

실행

public class FacadeTest {
    private Cinema cinema;
    private Client client;

    private static final String MOVIE_NAME = "어벤져스";

    @Before
    public void setUp() {
        cinema = new Cinema();
        client = new Client(50000);
    }

    @Test
    public void test() throws Exception {
        cinema.enterTheater(client, MOVIE_NAME);
    }
}

Cinema의 seller와 movieSet에 대한 getter가 제거 되었다.
외부에 공개될 필요가 없기 때문이다.
그리고 영화를 선택하고, 티켓을 구입할수 있는지 확인하는 절차 뒤에 티켓을 구입하여 상영관에 입장하는 로직도 실행클래스가 아닌 Cinema 클래스 내부로 들어갔다.
우리는 다음 한줄로 우리가 원하고자 하는 것을 명확하게 실행 할수 있다.

cinema.enterTheater(client, MOVIE_NAME);

언제 사용?

퍼사드 패턴은 하위 시스템에 여러 차례 호출하는 대신 퍼사드 클래스를 한번만 호출하면 된다.
위 예제에서 Cinema클래스가 퍼사드 클래스가 되는 것이다.
이는 보안과 단순함 측면에서 어플리케이션 내부 상세 및 실행 흐름을 캡슐화한다.

장점

  • 하위 시스템에 알 필요가 없으므로 결합도가 낮아짐
  • 코드가 직관적으로 되고, 코드 변경시 유지보수성이 좋아진다
  • 캡슐화가 되어 로직의 재사용성이 높아진다
  • 연관된 메서드를 한 메서드로 묶어 호출하므로 비즈니스 로직이 덜 복잡해진다
  • 보안 및 트랜잭션 관리를 중앙화한다
  • mock으로 테스트하기 쉬운 패턴으로 변경된다.

'Foundation > Design Pattern' 카테고리의 다른 글

Command Pattern  (0) 2019.11.02
Adapter 패턴과 Decorator 패턴의 차이  (0) 2019.10.27
Decorator 패턴  (0) 2019.10.27
Adapter 패턴  (0) 2019.10.27
Comments