14장. 기타 패턴
브리지 패턴
구현과 더불어 추상화 부분까지 변경해야 한다면 유용한 패턴
추상화된 부분과 구현 부분을 서로 다른 클래스 계층 구조로 분리해서 그 둘을 모두 변경할 수 있다.
브리지로 연결함으로써 양 쪽을 서로 독립적으로 변경해줄 수 있게 되었다.
브리지 패턴의 장점
- 구현과 인터페이스를 완전히 결합하지 않았기에 구현과 추상화 부분을 분리 가능
- 추상화된 부분과 실제 구현 부분을 독립적으로 확장 가능
- 추상화 부분을 구현한 구상 클래스가 바뀌어도 클라이언트에는 영향 X
브리지 패턴의 활용법과 단점
- 여러 플랫폼에서 사용해야 하는 그래픽스와 윈도우 처리 시스템에서 유용하게 쓰임
- 인터페이스와 실제 구현할 부분을 서로 다른 방식으로 변경해야 할 때 유용하게 쓰임
- 디자인이 복잡해진다는 단점
빌더 패턴
제품을 여러 단계로 나눠서 만들도록 제품 생산 단계를 캡슐화하고 싶다면 사용하는 패턴
빌더 패턴의 장점
- 복합 객체 생성 과정을 캡슐화
- 여러 단계와 다양한 절차를 거쳐 객체를 만들 수 있다 (팩토리 패턴은 한 단계에서 모든 걸 처리).
- 제품의 내부 구조를 클라이언트로부터 보호할 수 있음
- 클라이언트는 추상 인터페이스만 볼 수 있기에 제품을 구현한 코드를 쉽게 바꿀 수 있다.
빌더 패턴의 활용법과 단점
- 복합 객체 구조를 구축하는 용도로 많이 쓰임
- 팩토리를 사용할 때 보다 객체를 만들 때 클라이언트에 관해 더 많이 알아야 함
책임 연쇄 패턴
1개의 요청을 2개 이상의 객체에서 처리해야할 때 사용하는 패턴
책임 연쇄 패턴의 장점
- 요청을 보낸 쪽과 받는 쪽을 분리할 수 있다.
- 객체는 사슬의 구조를 몰라도 되고 그 사슬에 들어있는 다른 객체의 직접적인 레퍼런스를 가질 필요도 없으므로 객체를 단순하게 만들 수 있다.
- 사슬에 들어가는 객체를 바꾸거나 순서를 바꿈으로써 역할을 동적으로 추가하거나 제거할 수 있다.
책임 연쇄 패턴의 활용법과 단점
- 윈도우 시스템에서 마우스 클릭과 키보드 이벤트를 처리할 때 흔히 쓰임
- 요청이 반드시 수행된다는 보장이 없다는 단점 존재. 사슬 끝까지 갔는데도 처리되지 않을 수도 있다 (이런 특성이 장점이 될 수도 있음).
- 실행 시에 과정을 살펴보거나 디버깅하기가 힘들다는 단점 존재
플라이웨이트 패턴
어떤 클래스의 인스턴스 하나로 여러 개의 ‘가상 인스턴스’를 제공하고 싶은 경우 사용
플라이웨이트 패턴의 장점
- 실행 시에 객체 인스턴스의 개수를 줄여서 메모리를 절약
- 여러 ‘가상’ 객체의 상태를 한곳에 모아 둘 수 있.
플라이웨이트 패턴의 사용법과 단점
- 어떤 클래스의 인스턴스가 아주 많이 필요하지만 모두 똑같은 방식으로 제어해야 할 때 유용
- 일단 이 패턴을 써서 구현해 놓으면 특정 인스턴스만 다른 인스턴스와 다르게 행동하게 할 수 없다는 단점
인터프리터 패턴
어떤 언어의 인터프리터를 만들 때 사용
중재자 패턴
서로 관련된 객체 사이의 복잡한 통신과 제어를 한곳으로 집중하고 싶을 때 사용하는 패턴
중재자 패턴의 장점
- 시스템과 객체를 분리함으로써 재사용성을 획기적으로 향상시킬 수 있다.
- 제어 로직을 한 군데 모아놨으므로 관리하기가 수월함
- 시스템에 들어있는 객체 사이에서 오가는 메시지를 확 줄이고 단순화 가능
중재자 패턴의 활용법과 단점
- 서로 연관된 GUI 구성 요소를 관리하는 용도로 많이 쓰임.
- 디자인을 잘 하지 못하면 중재자 객체가 너무 복잡해질 수 있다는 단점
메멘토 패턴
객체를 이전의 상태로 복구해야할 때, 혹은 사용자의 '작업 취소' 요청할 때 사용하면 좋은 패턴
메멘토 패턴의 장점
- 저장된 상태를 핵심 객체와는 다른 별도의 객체에 보관할 수 있어 안전함
- 핵심 객체의 데이터를 계속해서 캡슐화된 상태로 유지 가능.
- 복구 기능을 구현하기가 쉽다.
메멘토 패턴의 활용법과 단점
- 메멘토 객체를 써서 상태를 저장.
- 자바 시스템에서는 시스템의 상태를 저장할 때 직렬화를 사용하는 것이 좋다.
- 상태를 저장하고 복구하는 데 시간이 오래 걸릴 수 있다는 단점
프로토타입 패턴
어떤 클래스의 인스턴스를 만들 때 자원과 시간이 많이 들거나 복잡할 때 사용하면 좋은 패턴
프로토타입 패턴의 장점
- 클라이언트는 새로운 인스턴스를 만드는 과정을 몰라도 된다.
- 클라이언트는 구체적인 형식을 몰라도 객체를 생성할 수 있다.
- 상황에 따라서 객체를 새로 생성하는 것보다 객체를 복사하는 것이 더 효율적일 수 있다.
프로토타입 패턴의 활용법과 단점
- 시스템에서 복잡한 클래스 계층구조에 파묻혀 있는 다양한 형식의 객체 인스턴스를 새로 만들어야 할 때 유용
- 때때로 객체의 복사본을 만드는 일이 매우 복잡할 수도 있다는 단점
비지터 패턴
다양한 객체에 새로운 기능을 추가해야 하는데 캡슐화가 별로 중요하지 않을 때 사용하면 좋은 패턴
- 비지터 객체는 트래버서(Traverser) 객체와 함께 돌아간다.
- 트래버서는 컴포지트 패턴을 쓸 때, 복합 객체 내에 속해 있는 모든 객체에 접근하는 일을 도와주는 역할 → 비지터 객체에서 복합 객체 내의 모든 객체를 대상으로 원하는 작업을 처리하게 해줌
- 각각의 상태를 모두 가져오면 클라이언트는 비지터에게 각 상태에 맞는 다양한 작업을 처리하도록 요구할 수 있다.
- 새로운 기능이 필요하게 되더라도 비지터만 고치면 되니까 편리함
즉, 각 복합 클래스에 상태를 노출하는 getter 가 있고, 중간에 VIsitor 클래스를 만들어서 각 복합 클래스의 getter 를 호출해 정보를 얻어온 후, 클라이언트에서 원하는 작업을 처리 (뭔가 프록시랑 비슷한 느낌, 그러나 프록시는 "제어" 가 목적이니까 용도가 조금 다른 듯,.)
비지터 패턴의 장점
- 구조를 변경하지 않으면서도 복합 객체 구조에 새로운 기능 추가 가능
- 비지터가 수행하는 기능과 관련된 코드를 한곳에 모아 둘 수 있다
비지터 패턴의 단점
- 비지터를 사용하면 복합 클래스의 캡슐화가 깨지게 된다.
- 컬렉션 내의 모든 항목에 접근하는 트래버서가 있으므로 복합 구조를 변경하기가 더 어려워짐
정리하며 들었던 고민과 자문자답
책임 연쇄 패턴
- 객체는 사슬의 구조를 몰라도 되고 그 사슬에 들어있는 다른 객체의 직접적인 레퍼런스를 가질 필요도 없으므로 객체를 단순하게 만들 수 있다.
사슬의 다른 객체에 대한 직접적인 레퍼런스가 없이 요청을 위임할 수가 있나..?
위 질문에 대한 자문 자답
클래스 다이어그램을 다시 보면
핸들러 공통 인터페이스(RemoteControl)에 Successor 가 있는데, 얘를 통해서 각 필터들의 레퍼런스를 저장하고 있을 듯.. 그래서 각 핸들러에서 handleRequest() 를 처리할 때, successor 에게 어떠한 메시지를 전송함으로써 다음 핸들러로 전달되게 해주지 않을까 싶다.
위 질문과 대답의 연장선으로 더 찾아본 내용
책임 연쇄패턴 의 예시: 스프링 필터
사용자 인증, 혹은 로깅 같은 공통 기능을 서블릿의 요청 처리 이전에 전처리하거나, 호출 후 후처리하고싶다면 서블릿 필터로 구현 가능
그런데 필터는 스프링 컨텍스트 밖에서 동작하는데, 커스텀 필터를 어떻게 등록할까?
스프링 컨텍스트에 진입 전에 필터 체인이 실행되는데, 필터 체인에 등록되어 있는 필터들은 서블릿 컨텍스트를 통해 접근 가능한 스프링 컨텍스트에 등록되어 있는 빈 객체여서 스프링 컨텍스트 밖에서 동작 가능한 것!!
(여태 필터와 인터셉터를 알고 있지만, 의문을 가지지 않았던 내용... 이번 기회에 궁금해져서 찾아볼 수 있었다.)
Filters usually don't load their own context but rather access service beans from the Spring root application context, accessible via the filter's ServletContext
Filter
Filter Chain 안에서의 순서를 지정 및 지정 순서에 따라서 동작하게 가능
- @Order(Num) 애너테이션을 통해 순서 지정
- FilterRegistrationBean 을 이용해 Filter의 순서 명시적 지정
@WebFilter(urlPatterns = "/myFilter")
@Order(1)
public class MyFilter implements Filter {
@Override
public void doFilter(
ServletRequest request,
ServletResponse response,
FilterChain chain
) throws IOException, ServletException {
// 전처리
request.setCharacterEncoding("UTF-8");
System.out.println("doFilter() 전....");
// 다음 필터로 요청이랑 응답 객체 넘기고, 필터 처리 수행
chain.doFilter(request, response);
// 후처리
System.out.println("doFilter() 후....");
}
}
FilterChain
요청 URI path를 기반으로 HttpServletRequest를 처리
체인에 사용되는 필터들은 Application Context 에 등록되어 있는 빈 객체를 주입시켜줌
<bean id="springSecurityFilterChain" class="org.springframework.security.web.FilterChainProxy">
<constructor-arg>
<list>
<sec:filter-chain pattern="/restful/**" filters="
securityContextPersistenceFilterWithASCFalse,
basicAuthenticationFilter,
exceptionTranslationFilter,
filterSecurityInterceptor" />
<sec:filter-chain pattern="/**" filters="
securityContextPersistenceFilterWithASCTrue,
formLoginFilter,
exceptionTranslationFilter,
filterSecurityInterceptor" />
</list>
</constructor-arg>
</bean>