[Design pattern] 2-7. 퍼사드 패턴 (Facade pattern)


Study/Design Pattern  2021. 11. 2. 19:29

안녕하세요. 명월입니다.


이 글은 디자인 패턴의 퍼사드 패턴(Facade pattern)에 대한 글입니다.


퍼사드 패턴은 디자인 패턴에서 가장 많이 사용되는 패턴 중의 하나로, 우리가 디자인 패턴을 모르고 가장 자연스럽게 작성되는 패턴이지 않을까 싶습니다.

이 패턴을 간단한게 설명하면 이전에 생성된 객체와 함수를 사양의 흐름에 맞게 배치하는 구조입니다.

출처 - https://en.wikipedia.org/wiki/Facade_pattern
#pragma once
#include <stdio.h>
#include <iostream>
using namespace std;
// ControllerA 클래스
class ControllerA {
public:
  // 함수
  void init() {
    // 콘솔 출력
    cout << "ControllerA init" << endl;
  }
  // 함수
  void run() {
    // 콘솔 출력
    cout << "ControllerA run" << endl;
  }
  // 함수
  void close() {
    // 콘솔 출력
    cout << "ControllerA close" << endl;
  }
};
// ControllerB 클래스
class ControllerB {
public:
  // 함수
  void get() {
    // 콘솔 출력
    cout << "ControllerB get" << endl;
  }
  // 함수
  void put() {
    // 콘솔 출력
    cout << "ControllerB put" << endl;
  }
};
// Facade 패턴 클래스
class WorkFlow {
private:
  // 퍼사드 패턴에서 사용될 맴버 변수
  ControllerA cona;
  ControllerB conb;
public:
  // runA 타입의 함수
  void runA() {
    // 사양 순서대로 함수를 실행
    this->cona.init();
    this->conb.get();
    this->cona.run();
    this->cona.close();
  }
  // runB 타입의 함수
  void runB() {
    // 사양 순서대로 함수를 실행
    this->conb.put();
    this->cona.run();
  }
};

// 실행 함수
int main() {
  // 인스턴스 생성
  WorkFlow work;
  // 콘솔 출력
  cout << "runA type execute." << endl;
  // Facade 패턴의 클래스의 runA 함수를 실행
  work.runA();
  // 콘솔 출력
  cout << endl << "runB type execute." << endl;
  // Facade 패턴의 클래스의 runB 함수를 실행
  work.runB();
  return 0;
}

위 예제를 설명하면, main 함수에서는, 퍼사드 패턴의 클래스에서 설정해 놓은 runA 던가 runB 함수를 실행하는 것으로, 사양에 의해 차례대로 실행하는 구조입니다.

즉, Facade 클래스에서는 처리되는 순서를 설정해서, main 함수에서는 runA 함수를 호출하는 것으로 처리가 개시되는 형태의 구조로 되어 있습니다.

// 인터 페이스
interface INode {
  // 추상 메소드
  void print();
}
// INode를 상속받은 Node1클래스
class Node1 implements INode {
  // 함수 재정의
  public void print() {
    // 콘솔 출력
    System.out.println("Node1 class");
  }
}
// INode를 상속받은 Node2클래스
class Node2 implements INode {
  // 함수 재정의
  public void print() {
    // 콘솔 출력
    System.out.println("Node2 class");
  }
}
// Facade 패턴이 포함되어 있는 클래스
class Controller {
  // 맴버 변수
  private INode node = null;
  // 맴버 변수의 인스턴스 생성
  private void createNode1() {
    // 인스턴스 생성
    node = new Node1();
  }
  // 맴버 변수의 인스턴스 생성
  private void createNode2() {
    // 인스턴스 생성
    node = new Node2();
  }
  // 실행
  private void execute() {
    // 맴버 변수가 null이 아니라면
    if (node != null) {
      // print 함수 실행
      node.print();
    } else {
      // null 인 경우 에러 처리
      throw new NullPointerException("Please class init.");
    }
  }
  // Node1클래스의 인스턴스를 생성하고 실행
  public void execType1() {
    // 인스턴스 생성
    createNode1();
    // 실행
    execute();
  }
  // Node2클래스의 인스턴스를 생성하고 실행
  public void execType2() {
    // 인스턴스 생성
    createNode2();
    // 실행
    execute();
  }
}
// 실행 클래스
class Program {
  // 실행 함수
  public static void main(String[] args) {
    // Contorller 인스턴스 생성
    var controller = new Controller();
    // execType1 함수를 실행
    controller.execType1();
    // execType2 함수를 실행
    controller.execType2();
  }
}

퍼사드 패턴이라고 따로 클래스를 생성할 필요는 없고 Controller의 형태에서 객체의 각 처리는 private로 처리하고, 클래스 외부에서는 퍼사드 패턴으로 접근이 가능하도록 execType1함수나 execType2함수처럼 만드는 것이 일반적입니다.

using System;
using System.Reflection;
using System.Linq;

// Node 클래스
class Node
{
  // 프로퍼티
  public string Data { get; set; }
}
// 추상 클래스
abstract class AController
{
  // 퍼사드 패턴의 함수
  public void Route(string url)
  {
    // 파라미터의 데이터를 /기준으로 나눈다.
    var route = url.Split('/');
    // 파라미터
    object data = null;
    // 나눈 데이터 개수가 1보다 클경우
    if (route.Length > 1)
    {
      // ?로 문자열을 나눈다.
      var ps = route[1].Split('?');
      // ps 개수가 0보다 큰경우
      if (ps.Length > 0)
      {
        // 파라미터 인스턴스를 생성한다.예: Node 클래스의 인스턴스 생성
        data = Type.GetType(ps[0]).GetConstructors(BindingFlags.Instance | BindingFlags.Public)
                   .FirstOrDefault()?.Invoke(null);
      }
      // 파라미터 데이터가 null이 아니고 나눈 파라미터의 개수가 1보다 큰 경우
      if (data != null && ps.Length > 1)
      {
        // 프로퍼티 취득
        var properties = data.GetType().GetProperties(BindingFlags.Instance | BindingFlags.Public);
        // &로 문자열을 나눈다.
        foreach (var p in ps[1].Split('&'))
        {
          // =로 문자열을 나눈다.
          var o = p.Split('=');
          // 2개일 경우
          if (o.Length == 2)
          {
            // 프로퍼티에 값을 넣는다.
            properties.Where(x => string.Equals(x.Name, o[0], StringComparison.OrdinalIgnoreCase))
                      .FirstOrDefault()?.SetValue(data, o[1]);
          }
        }

      }
    }
    // 나눈 개수가 1보다 클경우
    if (route.Length > 0)
    {
      // 메소드 찾기
      var methods = this.GetType().GetMethods(BindingFlags.Instance | BindingFlags.Public);
      // 실행
      methods.Where(x => string.Equals(x.Name, route[0], StringComparison.OrdinalIgnoreCase))
             .FirstOrDefault()?.Invoke(this, new object[] { data });
    }

  }
}
// AController 클래스를 상속받는다.
class Controller : AController
{
  // 함수
  public void Index(Node node)
  {
    // 콘솔 출력
    Console.WriteLine("Contorller Index is executed, node data - " + node.Data);
  }
}

// 실행 클래스
class Program
{
  // 실행 함수
  static void Main(String[] args)
  {
    // Contorller 인스턴스 생성
    var control = new Controller();
    // url를 입력하여 실행한다.
    control.Route("Index/Node?data=hello world");
    // 아무 키나 누르시면 종료합니다.
    Console.WriteLine("Press any key...");
    Console.ReadKey();
  }
}

실무에서 사용되는 퍼사드 패턴으로 예제를 만들었습니다.

사실 위에는 퍼사드 패턴만 있는 것이 아니고 인터프리터 패턴도 같이 사용되고 있습니다. 위에서 Main 함수에서 우리가 Web에서 자주 사용되는 url의 값을 넣었습니다.

사실 문자열 자르기는 정규식을 사용해서 만들어야 하는데.. 개인적으로 정규식이 굉장히 잘하는 분야도 아니고 생각하기 귀찮아서 그냥 Split으로 잘랐습니다.

즉, MVC 모델에서 위와 같이 웹 브라우저에서 호출이 오면 Route를 거쳐서 함수를 찾게 됩니다. 그리고 파라미터에 맞는 파라미터 클래스의 인스턴스도 생성하고 호출을 하게 되겠습니다.


우리는 그런 Facade 패턴으로 만들어져 있는 프레임워크에서 요청 메서드만 작성하면 됩니다. 즉, 프레임워크의 MVC 구조는 Facade 패턴으로 구현이 되어 있는 것입니다.

사실 Facade 패턴은 이 패턴을 모르더라도 이미 프로그램을 작성할 때, 많이 작성하는 방법입니다.

그러나 조금 이론적인 흐름을 알게 되면 위처럼 Reflection 기능까지 추가해서 응용이 가능한 패턴을 구현할 수 있습니다.


여기까지 디자인 패턴의 퍼사드 패턴(Facade pattern)에 대한 글이었습니다.


궁금한 점이나 잘못된 점이 있으면 댓글 부탁드립니다.