본문 바로가기
ComputerScience/Design Pattern

Facade Pattern

by 규난 2023. 2. 26.
728x90

Facade Pattern 이란?

하위 시스템의 복잡도를 감추는 동시에 그 전체 기능을 사용하기 쉬운 단순한 인터페이스를 제공하는 패턴입니다.

즉, 복잡한 비즈니스 로직을 상위 레벨의 인터페이스로 캡슐화해서 하위 시스템에 더 쉽게 접근할 수 있게 하며 보통 연관된 메서드 호출을 메소드 하나로 묶어 순서대로 실행시킵니다.  퍼사드 패턴을 싱글 추상 팩토리 패턴이라고도 부릅니다.

 

Facade Pattern이 쓰이는 경우

  • 클래스 드라이버 같은 API를 만들 때
  • 네트워크 호출을 줄일 때. 퍼사드 패턴은 하위 시스템을 여러 차례 호출하는 반면 클라이언트는 퍼사드 패턴을 딱 한 번만 호출합니다.
  • 보안과 단순함 측면에서 애플리케이션 내부 실행흐름을 캡슐화 할 때

Facade Pattern의 장점

  • 클라이언트가 하위 시스템을 알 필요가 없으므로 결합도가 낮아집니다.
  • 코드의 유지보수성, 관리성이 좋아집니다.
  • 로직을 다시 사용할 수 있어 기능을 재사용할 일이 많아집니다.
  • 여러번 실행해도 호출하는 메서드는 동일하기 때문에 일관된 서비스가 보장됩니다.
  • 연관된 메서드를 한 메서드로 묶어 호출하므로 비즈니스로직이 덜 복잡해집니다.
  • 테스트할 수 있고 mock을 쓸 수 있는 패턴으로 구현이 됩니다.

 

Facade Pattern이 적용된 java HttpSession

 

Controller api에서 session을 사용하는 코드

@GetMapping("/inquiryBalance")
public ApiResult<InquiryBalanceReq> inquiryBalance
	(@RequestBody InquiryBalanceReq inquiryBalanceReq, HttpSession session) {
    return success(inquiryBalanceReq);
}

위 코드 처럼 컨트롤러에서 HttpSession을 파라미터로 받게되면

DispatcherServlet의 doService 메소드 안에서 

if (this.flashMapManager != null) {
    FlashMap inputFlashMap = this.flashMapManager.retrieveAndUpdate(request, response);
    if (inputFlashMap != null) {
        request.setAttribute(INPUT_FLASH_MAP_ATTRIBUTE, Collections.unmodifiableMap(inputFlashMap));
    }
    request.setAttribute(OUTPUT_FLASH_MAP_ATTRIBUTE, new FlashMap());
    request.setAttribute(FLASH_MAP_MANAGER_ATTRIBUTE, this.flashMapManager);
}

flashMapManager가 null이 아닌 경우 AbstractFlashMapManager 클래스의 retrieveAndUpdate 메소드안에 retriebeFlashMaps (AbstractFlashMapManager를 상속 받은 SessionFlashMapManage의 retriebeFlashMaps 메소드)메소드가 실행이됩니다.

 

SessionFlashMapManager의 retrieveFlashMap 메소드

@Override
@SuppressWarnings("unchecked")
@Nullable
protected List<FlashMap> retrieveFlashMaps(HttpServletRequest request) {
    // RequestFacade의 getSession 호출
    HttpSession session = request.getSession(false);
    return (session != null ? (List<FlashMap>) session.getAttribute(FLASH_MAPS_SESSION_ATTRIBUTE) : null);
}

retrieveFlashMap 메소드를 보면 request.getSession(false)메소드가 실행되는것을 볼 수 있는데, 이 메소드가 실행되면서 RequestFacade의 getSession이 호출 되고 이 안에서 Request의 getSession이 호출 되면서 실질적인 HttpSession의 getSession이 실행되고 그 안에서 SessionFacade이 만들어지게 됩니다.

 

밑의 코드를 보시면 이해가 더 잘 되실겁니다.

 

RequestFacade의 getSession

@Override
public HttpSession getSession(boolean create) {

    if (request == null) {
        throw new IllegalStateException(
                        sm.getString("requestFacade.nullRequest"));
    }

    if (SecurityUtil.isPackageProtectionEnabled()){
        return AccessController.
            doPrivileged(new GetSessionPrivilegedAction(create));
    } else {
    	// Request의 getSession을 호출
        return request.getSession(create);
    }
}

 

 

Request의 getSession

@Override
public HttpSession getSession(boolean create) {
    Session session = doGetSession(create);
    if (session == null) {
        return null;
    }

    return session.getSession();
}

 

StandardSession의 getSession

@Override
public HttpSession getSession() {
    if (facade == null) {
    	// Session의 Facade 패턴 적용
        if (SecurityUtil.isPackageProtectionEnabled()) {
            facade = AccessController.doPrivileged(new PrivilegedNewSessionFacade(this));
        } else {
            facade = new StandardSessionFacade(this);
        }
    }
    return facade;
}

 

이러한 과정을 거쳐 Facade Pattern이 적용된 Session을 컨트롤러 파라미터로 받아서 사용할 수 있게 됩니다.

위 사진을 보시면 session 파라미터가 StandardSessionFacade 객체인 것을 확일 할 수 있습니다.

 

간단하게 Facade Pattern을 사용하면 좋은 예를 보여드리도록 하겠습니다.

StandardSessionFacade의 invalidate() 메소드를 보시면 StandardSession의 invalidate() 메소드를 호출하고 있습니다. 

@Override
public void invalidate() {
    session.invalidate();
}

 

StandardSession의 invalidate() 메소드의 내부 로직

사실 invalidate() 내부 로직은 상당히 복잡하게 동작하게 됩니다.

이렇게 Facade Pattern은 하위 시스템의 복잡도를 감추는 동시에 그 전체 기능을 사용하기 쉽도록 합니다.

@Override
public void invalidate() {

    if (!isValidInternal()) {
        throw new IllegalStateException
            (sm.getString("standardSession.invalidate.ise"));
    }

    // Cause this session to expire
    expire();
}

@Override
public void expire() {

    expire(true);

}

public void expire(boolean notify) {

    // Check to see if session has already been invalidated.
    // Do not check expiring at this point as expire should not return until
    // isValid is false
    if (!isValid) {
        return;
    }

    synchronized (this) {
        // Check again, now we are inside the sync so this code only runs once
        // Double check locking - isValid needs to be volatile
        // The check of expiring is to ensure that an infinite loop is not
        // entered as per bug 56339
        if (expiring || !isValid) {
            return;
        }

        if (manager == null) {
            return;
        }

        // Mark this session as "being expired"
        expiring = true;

        // Notify interested application event listeners
        // FIXME - Assumes we call listeners in reverse order
        Context context = manager.getContext();

        // The call to expire() may not have been triggered by the webapp.
        // Make sure the webapp's class loader is set when calling the
        // listeners
        if (notify) {
            ClassLoader oldContextClassLoader = null;
            try {
                oldContextClassLoader = context.bind(Globals.IS_SECURITY_ENABLED, null);
                Object listeners[] = context.getApplicationLifecycleListeners();
                if (listeners != null && listeners.length > 0) {
                    HttpSessionEvent event =
                        new HttpSessionEvent(getSession());
                    for (int i = 0; i < listeners.length; i++) {
                        int j = (listeners.length - 1) - i;
                        if (!(listeners[j] instanceof HttpSessionListener)) {
                            continue;
                        }
                        HttpSessionListener listener =
                            (HttpSessionListener) listeners[j];
                        try {
                            context.fireContainerEvent("beforeSessionDestroyed",
                                    listener);
                            listener.sessionDestroyed(event);
                            context.fireContainerEvent("afterSessionDestroyed",
                                    listener);
                        } catch (Throwable t) {
                            ExceptionUtils.handleThrowable(t);
                            try {
                                context.fireContainerEvent(
                                        "afterSessionDestroyed", listener);
                            } catch (Exception e) {
                                // Ignore
                            }
                            manager.getContext().getLogger().error
                                (sm.getString("standardSession.sessionEvent"), t);
                        }
                    }
                }
            } finally {
                context.unbind(Globals.IS_SECURITY_ENABLED, oldContextClassLoader);
            }
        }

        if (ACTIVITY_CHECK) {
            accessCount.set(0);
        }

        // Remove this session from our manager's active sessions
        manager.remove(this, true);

        // Notify interested session event listeners
        if (notify) {
            fireSessionEvent(Session.SESSION_DESTROYED_EVENT, null);
        }

        // Call the logout method
        if (principal instanceof TomcatPrincipal) {
            TomcatPrincipal gp = (TomcatPrincipal) principal;
            try {
                gp.logout();
            } catch (Exception e) {
                manager.getContext().getLogger().error(
                        sm.getString("standardSession.logoutfail"),
                        e);
            }
        }

        // We have completed expire of this session
        setValid(false);
        expiring = false;

        // Unbind any objects associated with this session
        String keys[] = keys();
        ClassLoader oldContextClassLoader = null;
        try {
            oldContextClassLoader = context.bind(Globals.IS_SECURITY_ENABLED, null);
            for (String key : keys) {
                removeAttributeInternal(key, notify);
            }
        } finally {
            context.unbind(Globals.IS_SECURITY_ENABLED, oldContextClassLoader);
        }
    }

}
728x90

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

Proxy Pattern  (0) 2023.03.13
Adaptor Pattern  (0) 2023.03.06
Strategy Pattern  (0) 2023.02.20
디자인 패턴 - 디자인 원칙  (0) 2023.02.14
Builder Pattern  (0) 2023.02.01