본문 바로가기
3학년 2학기 학사/소프트웨어 분석 및 설계

[소프트웨어 분석 및 설계] L19. Facade Pattern, Flyweight Pattern, Proxy Pattern

by whiteTommy 2024. 12. 13.
반응형

Facade Pattern

  • 라이브러리(또는 서브시스템)에 대해 사용하기 편하기 간편한 인터페이스를 구성하기 위한 구조 패턴
    • 라이브러리의 각 클래스와 메소드의 사용이 복잡하거나 바로 가져다 쓰기에는 어려울 때, 퍼사드 패턴으로 디테일은 내부로 묶고 사용자가 쓰기 쉽게 정리하는 것
  • 퍼사드 (facade) 라는 뜻은 건물의 정면이라는 뜻
    • 퍼사드 패턴이 내부는 숨기고 외관만 보여주는 것에 비유 (외관만 보고 전체를 이해) 
  • 퍼사드 패턴이 필요한 상황
    • 라이브러리의 여러 API 를 조합해서 쓰는 상황에서 클라이언트의 코드의 라이브러리 의존성이 높을 때
    • JAVA 로 email 을 보내려는 코드를 작성할 때 클라이언트는 email API 를 복잡하게 조합해야 함
      public static void main(String[] args) {
          String from = "example@mail.com";
          String to = "destination@mail.com";
          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");
              message.setText("This is a test email.");
              Transport.send(message);
          } catch (MessagingException e) {
              e.printStackTrace();
          }
      }
  • 퍼사드 패턴의 구조
  • 퍼사드 패턴의 예시
    • 클라이언트는 퍼사드 (EmailSender) 만 가지고 작업
      • 미리 정의된 API 를 쓰기 때문에 코드의 사용이 쉽고 간결해짐
      • 복잡한 API 사용의 실수를 줄일 수 있음
        public class EmailSender {
            private EmailSettings emailSettings;
        
            public EmailSender(EmailSettings emailSettings) {
                this.emailSettings = emailSettings;
            }
        
            public void sendEmail(EmailMessage emailMessage) {
                Properties properties = new Properties();
                properties.put("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.getTitle());
                    message.setText(emailMessage.getContent());
                    Transport.send(message);
                } catch (MessagingException e) {
                    e.printStackTrace();
                }
            }
        }
        
        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.setFrom("from@mail.com");
            emailMessage.setTo("to@mail.com");
            emailMessage.setTitle("email title");
            emailMessage.setContent("email content");
        
            emailSender.sendEmail(emailMessage);
        }
  • 사용 시기
    • 복잡한 서브 시스템에 대한 제한적이지만 간단한 인터페이스가 필요할 때
    • 서브 시스템과의 결합도가 높아 의존성을 줄일 필요가 있을 때
  • 장점
    • 서브 시스템 간의 의존 관계가 많을 경우 이를 감소시키고 의존성을 한 곳으로 모을 수 있음
    • 클라이언트가 퍼사드 클래스만 다루기 때문에 기능을 쉽게 이해하고 사용할 수 있음
  • 단점
    • 앱의 모든 클래스에 결합된 god object (client 와 모두 연관이있는 객체) 가 될 수 있음
    • 코드가 추가되어서 유지보수 측면에서 관리 대상이 늘어남

 

Flyweight Pattern

  • 메모리 사용량을 최소화하기 위해 재사용 가능한 객체를 공유할 수 있게 해주는 구조 패턴
    • 캐시 (cache) 개념을 도입하여 패턴화 한 것
    • 자주 변화하는 속성(extrinsit) 과 변하지 않는 속성(intrinsit) 으로 분리
    • 변하지 않는 속성은 캐시함 (따로 저장하고 재사용해 메모리를 아낌)
  • 플라이웨이트 패턴이 필요한 상황
    • Editor 프로그램에서 character 를 표현하는 객체의 예시
      • Nanum, 12 가 중복되는 경우 메모리 사용량이 늘어남
        public class Character {
            private char value;
            private String color;
            private String fontFamily;
            private int fontSize;
        
            public Character(char value, String color, String fontFamily, int fontSize) {
                this.value = value;
                this.color = color;
                this.fontFamily = fontFamily;
                this.fontSize = fontSize;
            }
        }
        
        public class Client{
            public static void main(String[] args){
            	Character c1 = new Character('h', "red", "Nanum", 12);
                Character c2 = new Character('e', "white", "Nanum", 12);
                Character c3 = new Character('l', "white", "Nanum", 12);
                Character c4 = new Character('l', "blue", "Nanum", 12);
                Character c5 = new Character('o', "white", "Nanum", 12);
            }
        }
  • 플라이웨이트 패턴의 구조 (simple version)
  • 플라이웨이트 패턴 적용 예시
    • 처음에만 만들고 나머지는 재사용한다.
      public final class Font {
          final String family; // 글꼴 이름
          final int size;      // 글꼴 크기
      
          public Font(String family, int size) {
              this.family = family;
              this.size = size;
          }
      
          public String getFamily() {
              return family;
          }
      
          public int getSize() {
              return size;
          }
      }
      
      public class Character {
          private char value;    // 문자
          private String color;  // 색상
          private Font font;     // 공유 객체 (intrinsic)
      
          public Character(char value, String color, Font font) {
              this.value = value;
              this.color = color;
              this.font = font;
          }
      }
      
      public class FontFactory {
          private Map<String, Font> cache = new HashMap<>();
      
          public Font getFont(String font) {
              if (cache.containsKey(font)) { // 캐시에 존재하면 반환
                  return cache.get(font);
              } else { // 새로 생성 후 캐시에 추가
                  String[] split = font.split(":"); // "family:size" 포맷
                  String family = split[0];
                  int size = Integer.parseInt(split[1]);
                  Font newFont = new Font(family, size);
                  cache.put(font, newFont); // 캐싱
                  return newFont;
              }
          }
      }
      
      public class Client {
          public static void main(String[] args) {
              FontFactory fontFactory = new FontFactory();
      
              Character c1 = new Character('h', "white", fontFactory.getFont("nanum:12"));
              Character c2 = new Character('e', "white", fontFactory.getFont("nanum:12"));
              Character c3 = new Character('l', "white", fontFactory.getFont("nanum:12"));
      
              // 동일한 Font 객체를 재사용하여 메모리 절약
          }
      }


  • 사용 시기
    • 메모리에 오래 상주하는 객체가 많이 생성되어 메모리 사용이 높을 때
    • 공통적인 인스턴스를 많이 생성하는 로직이 포함되는 경우
  • 장점
    • 메모리 사용량과 프로그램 속도를 개선할 수 있음
      • 인스턴스화(new) 하면 데이터 생성 및 메모리 적재에 시간이 소모 됨
  • 단점
    • 캐싱등을 처리 하기 위한 클래스 도입으로 코드의 복잡성이 올라감

 

 

Proxy Pattern

  • 대상 원본 객체에 대한 접근을 제어하거나 대리할 수 있도록 해주는 구조 패턴(proxy = 대리인)
    • 클라이언트가 대상 원본 객체를 직접 쓰는 것이 아니라 대리인을 거쳐 쓰는 개념
    • Client 가 Service 모종의 이유로 수정을 못한다. 부가 기능을 둔다.
    • 프록시는 한번만 감쌀 수 있다.
    • 아래와 같은 이유로 수정을 하고 싶은데, 원본 객체를 수정할 수 없을 때, 원래 객체와 같은 인터페이스를 가지는 proxy 를 두어 처리
      • 대상 클래스가 민감한 정보를 가지고 있어 권한에 따라 접근을 제한 하고 싶을 때
      • 인스턴스화 하기 무거워 lazy 초기화를 하고 싶을 때 등등
  • 프록시 패턴의 효과
    • 보안
      • 클라이언트 작업 권한에 따라 유효한 권한일 경우에만 전달
    • 캐싱
      • 데이터가 캐시에 아직 존재하지 않는 경우에만 작업이 실행되도록 할 수 있음
    • 데이터 유효성 검사
      • 입력을 원본 객체로 전달하기 전에 유효성을 미리 체크하게 할 수 있음
    • 지연 초기화
      • 원본 객체의 생성 비용이 비싼 경우 프록시가 필요할 때 생성하게 할 수 있음
    • 로깅
      • 메소드 호출과 상대 매개 변수에 대해 중간에 기록을 남길 수 있음
  • 프록시 패턴의 구조
  • 프록시 패턴의 적용 예시
  • 사용 시기
    • 기능을 추가하고 싶은데, 기존의 특정 객체를 수정할 수 없는 상황 일 때
      • 지연 초기화, 접근 제어, 로깅, 캐싱 등등
  • 장점
    • 원래 하는 기능을 유지하며 부가 기능을 원래 사용법과 같이 쓸 수 있음
    • OCP 준수
      • 기존 대상 객체 코드의 변경 없이 기능 확장
    • SRP 준수
      • 대상 객체는 자신 기능에 집중, 부가 기능은 프록시가 집중
  • 단점
    • 많은 프록시 클래스를 도입해야 하므로 코드의 복잡성이 증가
    • 프록시 클래스 자체에 들어가는 자원이 많아 진다면 처리 비용 증가

 

 

 

 

 

 

 

 

 

반응형