DEV ℧ Developer Diary

[DesignPattern] 퍼사드 (Facade)

퍼사드 (Facade)

한 서브시스템 내의 인터페이스 집합에 대한 획일화된 하나의 인터페이스를 제공하는 패턴으로, 서브세스템을 사용하기 쉽도록 상위수준의 인터페이스를 정의합니다.

동기

시스템을 서브시스템으로구조화하면 복잡성을 줄이는데에 큰 도움이 된다. 공통적인 설계 목표는 서브시스템들 사이의 의사소통 및 종속성을 최소화하려는 것이다.

이런 목표를 달성하도록 도와주는 패턴이 퍼사드 패턴으로 주어진 서브시스템의 일반적인 기능에 대한 단순화된 하나의 인터페이스를 제공하려는 것이다.

활용 방안

퍼사드 패턴은 다음의 경우에 사용한다.

  • 시스템 범위가 확장되거나 구체적으로 설계되면 서브시스템은 계속 복잡해진다. 이러한 복잡한 서브시스템에 대한 단순한 인터페이스 제공이 필요할 때 활용된다.
  • 추상 개념에 대한 구현 클래스와 사용자 사이에 너무 많은 종속성이 종속할때 퍼사드의 사용을 통해 사용자와 다른 서브시스템간의 결합도를 줄일 수 있다.
  • 서브시스템을 계층화시킬 때. 퍼사드 패턴을 사용하여 각 서브시스템의 계층에 대한 접근점을 제공한다.

구조

파사드 구조

  • 퍼사드: 단순하고 일관된 통합 인터페이스를 제공하며, 서브시스템을 구성하는 어떤 클래스가 어떤 요청을 처리해야 하는지 알고 있으며, 사용자의 요청을 해당 서브시스템 객체에 전달한다.
  • 서브시스템 클래스들: 서브시스템의 기능을 구현하고, Facade 객체로 할당된 작업을 실제로 처리하지만 Facade에 대한 아무런 정보가 없다.

결과

퍼사드 패턴을 사용하면 얻는 이익은 다음과 같다.

  1. 서브시스템의 구성요소를 보호할 수 있다.
  2. 서브시스템과 사용자 코드간의 결합도를 더욱 약하게 만든다. 서브시스템 내 정의된 요소들은 강하게 결합될 수 있다. 서브시스템과 사용자 간의 결합이 약하면, 서브시스템 내의 요소를 다양화하는 작업을 원할하게 할 수 있다.
  3. 응용프로그램 쪽에서 서브시스템 클래스를 사용하는 것을 완전히 막지는 않는다.

장점과 단점

장점

  • 서브 시스템에 대한 의존성을 한곳으로 모을 수 있다.

단점

  • 퍼사드 클래스가 서브 시스템에 대한 모든 의존성을 가지게 된다.

구현

예시는 이메일 라이브러리 코드를 가져왔다. 적용 전의 Client 코드는 javax.mail 라이브러리에 의존성이 강하게 연결되어있다.

Facade 패턴을 적용하여 이메일 라이브러리에 대한 의존성을 많이 줄이고, 느슨한 결합의 코드로 만들어보고자 한다.

적용전

Client는 javax 이메일 라이브러리를 적용한 전체의 코드이다.

import javax.mail.Message;
import javax.mail.MessagingException;
import javax.mail.Session;
import javax.mail.Transport;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeMessage;
import java.util.Properties;

public class Client {

    public static void main(String[] args) {
        String to = "mho@google.me";
        String from = "mho@naber.me";
        String host = "127.0.0.1";

        Properties properties = System.getProperties();
        properties.setProperty("mail.smtp.host", host);

        Session session = Session.getDefaultInstance(properties);

        try {
            MimeMessage message = new MimeMessage(session);
            message.setFrom(new InternetAddress(from));
            message.addRecipient(Message.RecipientType.TO, new InternetAddress(to));
            message.setSubject("Test Mail from Java Program");
            message.setText("message");

            Transport.send(message);
        } catch (MessagingException e) {
            e.printStackTrace();
        }
    }
}

적용후

추상화에 대한 설계는 어떤 부분을 분리해 추상화를 진행할지에 대한 고민이 필요하다.

예시는 Client의 코드중 아래의 구분에 따라 추상화를 진행하고자 한다.

  • 이메일의 전체적인 메시지 정보인 발신자, 수신자, 제목, 내용 을 묶어 EmailMessage 클래스
  • 이메일 서버(Host)의 내용은 EmailSettings 클래스
  • 이메일을 전송하는 부분은 EmailSender 클래스

먼저, 이메일 메시지 정보 EmailMessage클래스와 이메일 서버의 정보를 가지고 있는 EmailSettings 클래스를 정의한다.

EmailMessage

public class EmailMessage {
    private String from;
    private String to;
    private String subject;
    private String text;

    public String getFrom() {
        return from;
    }

    public void setFrom(String from) {
        this.from = from;
    }

    public String getTo() {
        return to;
    }

    public void setTo(String to) {
        this.to = to;
    }

    public String getSubject() {
        return subject;
    }

    public void setSubject(String subject) {
        this.subject = subject;
    }

    public String getText() {
        return text;
    }

    public void setText(String text) {
        this.text = text;
    }
}

EmailSettings

public class EmailSettings {
    private String host;

    public String getHost() {
        return host;
    }

    public void setHost(String host) {
        this.host = host;
    }
}

각각의 필드를 살펴보면 적용전의 Client코드에서 사용했던 필드들을 가져와 클래스로 구성해 주었다.

이제 Client에서 메일을 발송하던 부분을 EmailSender 클래스로 정의해준다.

EmailSender

public class EmailSender {
    private final EmailSettings emailSettings;

    public EmailSender(EmailSettings emailSettings) {
        this.emailSettings = emailSettings;
    }

    public void sendEmail(EmailMessage emailMessage) {
        Properties properties = System.getProperties();
        properties.setProperty("mail.smtp.host", emailSettings.getHost());

        Session session = Session.getDefaultInstance(properties);

        try {
            MimeMessage message = new MimeMessage(session);
            message.setFrom(new InternetAddress(emailMessage.getFrom()));
            message.addRecipient(Message.RecipientType.TO, new InternetAddress(emailMessage.getTo()));
            message.setSubject(emailMessage.getSubject());
            message.setText(emailMessage.getText());

            Transport.send(message);
        } catch (MessagingException e) {
            e.printStackTrace();
        }
    }
}

EmailSender 클래스는 앞서 설정한 EmailSettings를 생성자로 받아 이메일의 서버 설정을 셋팅한다.

이후 sendEmail 메소드에서 이메일을 발송하기 위해 만든 이메일 정보 클래스 EmailMessage 클래스를 받아 이메일을 전송해준다.

이제 Client 코드를 살펴보도록 하자.

public class Client {
    public static void main(String[] args) {
        EmailSettings emailSettings = new EmailSettings();
        emailSettings.setHost("127.0.0.1");
        EmailSender emailSender = new EmailSender(emailSettings);
        EmailMessage emailMessage = new EmailMessage();
        emailMessage.setTo("mho@google.me");
        emailMessage.setFrom("mho@naber.me");
        emailMessage.setSubject("Facade Pattern Java Email Send Program");
        emailMessage.setText("Is message");
        emailSender.sendEmail(emailMessage);
    }
}

각각의 이메일 서버 정보와 이메일 정보를 EmailSettings 클래스와 EmailMessage 클래스에 정보를 넣어 EmailSender 클래스로 전달해 메일을 전송해준다.

적용전 Client 코드를 비교해본다면 기존에 import 되었던 javax.mail 라이브러리를 Client에서 의존성을 걷어내 느슨히 결합된 코드로 변경한 것을 확인할 수 있을 것이다.