architecture
프론트엔드 디자인 패턴 — Observer, Module, Composite
"어떤 디자인 패턴을 사용해보셨나요?"는 시니어 면접의 단골 질문입니다. GoF의 23개 패턴을 암기하는 것이 아니라, 프론트엔드 맥락에서 어떤 패턴이 자연스럽게 쓰이는지를 이해해야 합니다. Observer(Pub/Sub)는 이벤트 시스템의 근간이고, Composite는 React 컴포넌트 트리의 철학이며, Singleton은 전역 상태의 패턴입니다.
학습 개요
탄생 배경
해결하려 했던 문제
1994년 GoF(Gang of Four)가 "Design Patterns: Elements of Reusable Object-Oriented Software"를 출판하면서 객체지향 설계의 반복 패턴을 체계화했습니다. 당시 주류는 C++/Java 객체지향 프로그래밍이었으나, 이 패턴들의 핵심 아이디어는 언어를 초월합니다. 프론트엔드에서도 이벤트 버스(Observer), 컴포넌트 트리(Composite), Redux(Singleton + Observer) 등으로 구현됩니다.
역사적 맥락
jQuery 시대에는 Pub/Sub 패턴이 이벤트 통신의 표준이었습니다. Backbone.js가 MVC를 웹에 도입했고, AngularJS가 Dependency Injection(팩토리 패턴)을 대중화했습니다. React가 Composite 패턴을 컴포넌트 모델로 채택했고, Redux는 Flux 아키텍처를 Singleton Store로 구현했습니다. 현대 프론트엔드는 의식하지 못해도 수많은 디자인 패턴 위에서 동작합니다.
이전에는 어떻게 했나
함수형 프로그래밍 커뮤니티에서는 GoF 패턴 대부분이 일급 함수(First-class functions)로 더 간결하게 표현된다고 주장합니다. 예를 들어 Strategy 패턴은 함수를 인자로 전달하는 것으로 충분하고, Observer는 RxJS의 Observable로 더 강력하게 표현됩니다. 패턴은 도구이지 목표가 아닙니다.
멘탈 모델
동작 원리
프론트엔드에서 핵심 패턴 5가지: 1. Observer (Pub/Sub) 발행자(Subject)가 상태 변경 시 구독자(Observer)에게 알림 사용처: DOM 이벤트, EventEmitter, Redux, React Context, RxJS 2. Composite 단일 객체와 컬렉션을 동일하게 취급 사용처: React 컴포넌트 트리 (단일 컴포넌트 = 컴포넌트 그룹) 3. Singleton 인스턴스를 하나만 보장 사용처: Redux Store, Zustand Store, 전역 설정, API 클라이언트 4. Strategy 알고리즘을 런타임에 교환 가능하도록 캡슐화 사용처: 정렬 로직 교체, 인증 전략, 폼 유효성 검사 5. Decorator 객체에 동적으로 기능 추가 사용처: HOC(Higher-Order Component), TypeScript 데코레이터, middleware
핵심 구성 요소
Observer 패턴
느슨한 결합(loose coupling). 발행자가 구독자를 몰라도 됨
Composite 패턴
트리 구조. 단일 노드와 복합 노드를 동일 인터페이스로
Singleton 패턴
전역 상태의 일관성 보장. 테스트 어려움이 단점
Strategy 패턴
행동 캡슐화. 조건문 대신 전략 교체로 분기 처리
Decorator/HOC
원본 수정 없이 기능 확장. 조합 가능한 기능 레이어
Factory 패턴
객체 생성 로직 캡슐화. React.createElement가 팩토리 함수
흐름 설명
[Observer 패턴 — EventEmitter 구현]
class EventEmitter {
constructor() { this.listeners = {}; }
on(event, callback) {
if (!this.listeners[event]) this.listeners[event] = [];
this.listeners[event].push(callback);
}
emit(event, data) {
this.listeners[event]?.forEach(cb => cb(data));
}
off(event, callback) {
this.listeners[event] = this.listeners[event]?.filter(cb => cb !== callback);
}
}
[Composite — React 컴포넌트]
// 단일 컴포넌트와 컴포넌트 그룹을 동일하게 렌더링
<Button /> // 단일
<ButtonGroup> // 복합 (Composite)
<Button />
<Button />
</ButtonGroup>
[Strategy — 폼 유효성 검사]
const strategies = {
email: (v) => /^[^@]+@[^@]+$/.test(v),
required: (v) => v.trim().length > 0,
minLength: (min) => (v) => v.length >= min,
};
코드 예제
// ✅ Observer 패턴 — React Context로 구현
const ThemeContext = createContext(null);
function ThemeProvider({ children }) {
const [theme, setTheme] = useState('light');
return (
<ThemeContext.Provider value={{ theme, setTheme }}>
{children}
</ThemeContext.Provider>
);
}
// 구독자들이 context 변경을 자동으로 받음
// ✅ Strategy 패턴 — 정렬 전략 교체
function sortItems(items, strategy = 'name') {
const strategies = {
name: (a, b) => a.name.localeCompare(b.name),
date: (a, b) => new Date(b.date) - new Date(a.date),
price: (a, b) => a.price - b.price,
};
return [...items].sort(strategies[strategy]);
}
// ✅ Decorator — HOC 패턴
function withAuth(WrappedComponent) {
return function AuthComponent(props) {
const { user } = useAuth();
if (!user) return <Redirect to="/login" />;
return <WrappedComponent {...props} />;
};
}
const ProtectedDashboard = withAuth(Dashboard);
비교 분석
프론트엔드
vsObserver vs Pub/Sub
프론트엔드
vsHOC vs Custom Hook
트레이드오프
이상적인 사용 사례