| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 1 | 2 | 3 | 4 | 5 | 6 | 7 |
| 8 | 9 | 10 | 11 | 12 | 13 | 14 |
| 15 | 16 | 17 | 18 | 19 | 20 | 21 |
| 22 | 23 | 24 | 25 | 26 | 27 | 28 |
| 29 | 30 | 31 |
Tags
- DP
- 게임 서버 아키텍처
- PROJECT
- 비트마스킹
- Behavior Design Pattern
- trie
- Prisma
- 트라이
- map
- ccw 알고리즘
- 강한 연결 요소
- reference counting
- SCC
- 2-SAT
- JavaScript
- Strongly Connected Component
- R 그래프
- 그래프 탐색
- Github
- Express.js
- 이분 탐색
- 최소 공통 조상
- 자바스크립트
- 비트필드를 이용한 dp
- Delete
- Lock-free Stack
- 벨만-포드
- Spin Lock
- Binary Lifting
- localstorage
Archives
- Today
- Total
dh_0e
[Design Pattern] Structural Design Patterns III (Facade Pattern, Flyweight Pattern, Proxy Pattern) 본문
Software Analysis & Design/Design Patterns
[Design Pattern] Structural Design Patterns III (Facade Pattern, Flyweight Pattern, Proxy Pattern)
dh_0e 2025. 11. 12. 21:30
Facade Pattern(퍼사드 패턴)
- 라이브러리(or 서브시스템)에 대해 사용하기 간편한 인터페이스를 구성하기 위한 구조 패턴
- 라이브러리의 각 클래스와 메서드의 사용이 복잡하거나 바로 가져다 쓰기에는 어려울 때, 퍼사드 패턴으로 디테일은 내부로 묶고 사용자가 쓰기 쉽게 정리하는 것
- Facade: 건물의 정면
- 내부는 숨기고 외관만 보여주는 것에 비유(외관만 보고 전체를 이해)
- 필요한 상황: 라이브러리의 여러 API를 조합해서 쓰는 상황에서 클라이언트의 코드의 라이브러리 의존성이 높을 때


Facade Pattern 구조

- Facade: 클라이언트가 편리하게 사용할 수 있도록 서브시스템의 기능 조합을 모아둠
- Subsystem Classes: 실제 복잡한 기능을 수행하는 클래스들
- Client: 서브시스템 객체를 직접 호출하지 않고 퍼사드를 통해 작업


- 클라이언트는 퍼사드(EmailSender)만 가지고 작업
- 미리 정리된 API를 쓰기 때문에 코드의 사용이 쉽고 간결해짐
- 복잡한 API 사용의 실수를 줄일 수 있음
- 서브시스템 클래스: Properties, Session, MimeMessage, Transport 등
- 사용 시기
- 복잡한 서브 시스템에 대한 제한적이지만 간단한 인터페이스가 필요할 때
- 서브 시스템과의 결합도가 높아 의존성을 줄일 필요가 있을 때
- 장점
- 서브 시스템 간의 의존 관계가 많을 경우 이를 감소시키고 의존성을 한 곳으로 모을 수 있음
- 클라이언트가 퍼사드 클래스만 다루기 때문에 기능을 쉽게 이해하고 사용할 수 있음
- 장점이 과해지면 단점이 됨
- 단점
- 앱의 모든 클래스에 결합된 god object(만능 객체)가 될 수 있음
- 퍼사드 클래스 하나가 모든 것을 아는 객체가 되버리면 클래스가 너무 비대해져서 수정하기 어렵고, 변경에 취약해짐
- 관리 대상 증가: 코드가 추가되어서 유지보수 측면에서 관리 대상이 늘어남
- 앱의 모든 클래스에 결합된 god object(만능 객체)가 될 수 있음
Flyweight Pattern(플라이웨이트 패턴)
- 메모리 사용량을 최소화하기 위해 재사용 가능한 객체를 공유할 수 있게 해주는 구조 패턴
- 캐시(chache) 개념을 도입하여 패턴화 함
- 자주 변화하는 속성(extrinsit)과 변하지 않는 속성(instrinsit)으로 분리
- 변하지 않는 속성을 캐시함 (따로 저장하고 재사용해 메모리를 아낌)
- 필요한 상황
- ex) Edtitor 프로그램에서 character를 표현하는 객체


- Flyweight: 공유할 수 있는 부분이 포함됨(Intrinsic state: 변하지 않는 속성)
- Context: Flyweight 객체와 고유한 상태(Extrinsic state: 변하는 속성)를 가짐
- FlyweightFactory: 캐시를 이용하여 Flyweight Pool을 관리하고, 요청된 객체가 이미 캐시에 있으면 그것을 반환
- 위에 코드면 Aggregation(집합)


- cache가 font를 포함하고 있으면 저장해논 font 객체를 cache.get(font)해서 return
- 없으면 newFont 생성해서 return 후 cache.put(font, newFont)해서 저장
- 사용 시기
- 메모리에 오래 상주하는 객체가 많이 생성되어 메모리 사용이 높을 때
- 공통적인 인스턴스를 많이 생성하는 로직이 포함되는 경우
- 장점
- 메모리 사용량과 프로그램 속도를 개선할 수 있음
- 인스턴스화(new)를 계속 하면 데이터 생성 및 메모리 적재에 시간이 소모됨
- 메모리 사용량과 프로그램 속도를 개선할 수 있음
- 단점
- 캐싱을 처리하기 위한 클래스 도입으로 코드의 복잡성이 올라감
- 재사용이 안 되면 캐싱을 하는 의미가 없음(적재적소에 사용해야 함)
Proxy Pattern(프록시 패턴)
- proxy = 대리인
- 대상 원본 객체에 대한 접근을 제어하거나 대리할 수 있도록 해주는 구조 패턴
- 클라이언트가 대상 원본 객체를 직접 쓰는 것이 아니라 대리인을 거쳐 쓰는 개념
- 객체 수정을 하고 싶은데 원본 객체를 수정할 수 없을 때, 원래 객체와 같은 인터페이스를 가지는 proxy를 두어 처리
- 대상 클래스가 민감한 정보를 가지고 있어 권한에 따른 접근을 제어하고 싶을 때
- 인스턴스화 하기 무거워 lazy 초기화를 하고 싶을 때 등
- lazy 초기화: 필요해질 때까지 객체를 만들거나 값을 계산하지 않고 미뤄두는 기법
- COW?!
- proxy가 실제 객체 자리를 비어놨다가 처음 요청이 올 때 new 해서 보관함
- lazy 초기화: 필요해질 때까지 객체를 만들거나 값을 계산하지 않고 미뤄두는 기법

Proxy Pattern의 효과
- 보안: 클라이언트 작업 권한에 따라 유효한 권한 일 경우에만 전달
- 캐싱: 데이터가 캐시에 아직 존재하지 않는 경우에만 작업이 실행 되도록 할 수 있음
- 데이터 유효성 검사: 입력을 원본 객체로 전달하기 전에 유효성을 미리 체크하게 할 수 있음
- 지연(lazy) 초기화: 원본 객체의 생성 비용이 비싼 경우 프록시가 필요할 때 생성하게 할 수 있음
- 로깅: 메소드 호출과 상대 매개 변수에 대해 중간에 기록을 남길 수 있음
Proxy Pattern 구조

- ServiceInterface: 프록시와 서비스 객체가 따라야 하는 공통 인터페이스(Service의 환경과 동일하게 상속받게끔 함)
- Service(Real Subject): 유용한 로직을 제공하는 원본 객체
- Proxy: Service 객체를 가리키는 참조 필드가 있으며, 요청의 처리를 완료한 후 원본 서비스에 전달하거나 자체적으로 처리
- Service를 포함(집합)하고 있음

- ex) 미국에서 해저 터널을 통해 데이터를 가져오기까지 상대적으로 긴 시간이 걸리므로 한국 서버를 만들어 캐시에다 저장
- 위 예시에선 Proxy가 Interface를 참조하고 있음 (구조 예시에선 Service를 참조)
- 프록시는 인터페이스에만 의존하고, 실제 구현은 바꿔 끼울 수 있게 설계된 것으로 이게 더 유연한 설계임
- 어차피 Service를 가져오게 설계됨
- Service가 Interface를 상속 받으면서 오버라이딩한 내부 구현도 모두 가지고 올 수 있음
import java.util.HashMap;
// 1. 공통 인터페이스 (Subject)
interface ThirdPartyYouTubeLib {
String listVideos();
String getVideoInfo(int id);
}
// 2. 실제 서비스 (Real Subject) - 엄청 느린 유튜브 서버라고 가정
class ThirdPartyYouTubeClass implements ThirdPartyYouTubeLib {
@Override
public String listVideos() {
// 네트워크 지연 시뮬레이션 (1초 걸림)
simulateNetworkLatency();
System.out.println("[Real Service] 유튜브 서버에서 비디오 목록 다운로드 중...");
return "영상1, 영상2, 영상3";
}
@Override
public String getVideoInfo(int id) {
simulateNetworkLatency();
System.out.println("[Real Service] 유튜브 서버에서 영상 " + id + " 정보 다운로드 중...");
return "영상 " + id + "의 상세 정보";
}
private void simulateNetworkLatency() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
// 3. 프록시 (Proxy) - 캐싱 기능 추가
class CachedYouTubeClass implements ThirdPartyYouTubeLib {
private ThirdPartyYouTubeLib service; // 실제 서비스 객체
private String listCache; // 목록 캐시 저장소
private HashMap<Integer, String> videoCache = new HashMap<>(); // 비디오 정보 캐시 저장소
public boolean needReset;
public CachedYouTubeClass(ThirdPartyYouTubeLib service) {
this.service = service;
}
@Override
public String listVideos() {
// 1. 캐시가 없거나 초기화가 필요하면
if (listCache == null || needReset) {
// 2. 실제 서비스(위임)에게 요청해서 받아옴
listCache = service.listVideos();
System.out.println("[Proxy] 캐시에 저장 완료!");
} else {
// 3. 캐시가 있으면 바로 반환
System.out.println("[Proxy] 캐시된 목록 반환 (빠름!)");
}
return listCache;
}
@Override
public String getVideoInfo(int id) {
// 1. 해당 ID의 영상이 캐시에 없으면
if (!videoCache.containsKey(id) || needReset) {
// 2. 실제 서비스 호출 후 맵에 저장
videoCache.put(id, service.getVideoInfo(id));
System.out.println("[Proxy] 영상 " + id + " 캐시에 저장 완료!");
} else {
System.out.println("[Proxy] 캐시된 영상 " + id + " 반환 (빠름!)");
}
return videoCache.get(id);
}
}
// 4. 클라이언트 (Client)
class YouTubeManager {
protected ThirdPartyYouTubeLib service;
public YouTubeManager(ThirdPartyYouTubeLib service) {
this.service = service;
}
public void renderVideoPage(int id) {
System.out.println(service.getVideoInfo(id));
}
public void renderListPanel() {
System.out.println(service.listVideos());
}
}
- 위 예시는 복사하여 불러오기를 Proxy를 사용하여 처리했으며, Proxy로 수정이나 테스트 등을 해볼 수 있음
- 사용 시기
- 기능을 추가하고 싶은데, 기존의 특정 개체를 수정할 수 없는 상황일 때 Proxy를 만들어 수정
- 기능 수정 사항: 지연 초기화, 접근 제어, 로깅, 캐싱 등등
- 장점
- 원래 하는 기능을 유지하며 부가 기능을 원래 사용법과 같이 쓸 수 있음
- OCP(개방 폐쇄 원칙) 준수: 기존 대상 객체 코드의 변경 없이 기능 확장
- SRP(단일 객체 원칙) 준수: 대상 객체는 자신 기능에 집중할 수 있으며, 부가 기능은 프록시가 집중
- 단점
- 많은 프록시 클래스를 도입해야 하므로 코드의 복잡성이 증가
- 프록시 클래스 자체에 들어가는 자원이 많아진다면 처리 비용 증가
- 기능을 추가하고 싶은데, 기존의 특정 개체를 수정할 수 없는 상황일 때 Proxy를 만들어 수정
Summary

- Facade Pattern
- 라이브러리에 대해 사용하기 간편한 인터페이스를 구성하기 위한 구조 패턴
- Flyweight Pattern
- 메모리 사용량을 최소화하기 위해 재사용 가능한 객체를 캐싱하여 공유할 수 있게 해주는 구조 패턴
- Proxy Pattern
- 대상 원본 객체에 대해 대리할 수 있도록 해주는 구조 패턴
