2025-10-23

오늘 배운 것

디자인 패턴

프로그램을 설계할 때 발생했던 문제점들을 객체 간의 상호 관계 등을 이용하여 해결할 수 있도록 하나의 ‘규약’으로 정의한 것

싱글톤(Singleton) 패턴

하나의 클래스에 대해 오직 하나의 인스턴스만 생성하고, 전역적으로 공유하는 패턴

대표적으로 DB Connection Pool, 로그 관리 객체, 설정 정보 객체 등에 사용된다

✅ 장점

  • 인스턴스 생성 비용 절감
    • 객체를 매번 생성하지 않아도 되므로 메모리/시간 비용 감소
  • 전역 상태 공유 가능
    • 공통 리소스를 일관되게 유지하기 쉬움

⚠️ 단점

  • 의존성 증가 (Strong Coupling)
    • 싱글톤 객체에 직접 의존하면 테스트·교체·확장이 어려움
  • TDD/Testability 저하
    • 단위 테스트는 독립성과 순서 무관 실행이 중요한데, 전역 공유 객체는 상태가 남거나 격리가 어렵다

⇒ 이 문제를 해결하기 위해 등장하는 개념이 바로 DI(Dependency Injection)


의존성 주입(Dependency Injection)과의 관계

싱글톤 패턴 자체는 “전역 인스턴스 공유”를 해결하지만

그 과정에서 특정 객체에 대한 직접 의존성이 강해지는 문제를 만든다.

Test / 교체 / 확장 가능한 구조를 만들기 위해

하위 객체를 직접 생성하지 않고 외부에서 “주입”받는 방식으로 해결한다.

DI 적용 효과

  • 실제 서비스 객체 ↔ 가짜(Mock) 객체 교체 쉬움 ⇒ 단위테스트 가능
  • 특정 구현체를 갈아끼우더라도 상위 코드 수정 ↓ ⇒ 유연한 설계
  • 의존성 구조가 명시적으로 드러나 구조 이해 쉬움

부작용

  • 클래스 수 증가 → 구조 복잡도 증가
  • 런타임 바인딩으로 인한 약간의 오버헤드

의존 역전 원칙(DIP)과의 연결

DI는 단순 기법이 아니라 DIP(Dependency Inversion Principle) 의 구현이다.

상위 모듈은 하위 모듈의 구현에 의존하지 않는다

둘 다 추상화(Interface)에 의존해야 한다

추상화는 구체 구현에 의존하지 않는다

즉,

사용 측에서는 “어떤 객체인지” 모르고

“그 객체가 보장하는 인터페이스”만 알고 다루는 구조로 설계하라는 원칙이다.


정리

항목 핵심 요약
Singleton 인스턴스 1개 공유로 비용 절감 & 리소스 일관성 유지
문제 강한 결합·테스트 어려움·유연성 부족
해결 DI 사용으로 의존성 역전 + 테스트 가능 구조
철학 구현이 아니라 추상화에 의존하라 (DIP)

결론적으로,

싱글톤은 흔하지만 그대로 쓰면 테스트 불가 & 유지보수 지옥으로 이어질 수 있다.

DI+DIP를 통해 “관리 가능한 싱글톤 활용”을 만들어내는 게 현대 프레임워크(Spring 등)의 핵심 설계 철학이다.

팩토리(Factory) 패턴

객체 생성 로직을 사용하는 코드로부터 분리하여 생성 책임을 별도 영역으로 위임하는 패턴

상위(추상) 클래스가 전체 구조/규약을 정의하고, 하위(구체) 클래스가 어떤 객체를 생성할지 결정한다.


왜 쓰는가

  • 객체 생성 new 자체가 코드 내에 박혀 있으면,
    • 교체가 어렵고
    • 테스트하기 어렵고
    • 확장할 때 기존 코드 수정이 발생한다

Factory가 생성 책임을 갖고, 클라이언트는 ‘무엇을 만들지’ 모른 채 ‘만들어진 것만’ 사용한다.


장점

  • 느슨한 결합 (Low Coupling)
    • 사용자는 어떤 구현체가 생성되는지 모름
    • 상위(인터페이스/추상 클래스)와 하위(구현 클래스)가 분리
  • 유연성 증가
    • 새 클래스 추가 시 factory만 수정하면 클라이언트는 영향 없음
  • 유지보수성 향상
    • 객체 생성 로직이 한 곳에 모여 있어 리팩터링·정책 변경 시 한 지점만 수정

DI와의 관계

팩토리 패턴은 본질적으로

외부에서 객체(구현체)를 생성하여 주입한다”는 점에서 DI와 철학적으로 연결된다.

  • 클라이언트는 구현체 정보를 모르고 주입된 추상화에만 의존
  • DIP(의존 역전 원칙) 구현을 위한 전형적 전단계
  • 스프링 컨테이너의 Bean 생성/주입 과정 자체가 확장된 Factory 역할

쉽게 말하면,

DI가 “누가 넣어줄지”를 해결한다면,

Factory는 “무엇을 어떻게 만들지”를 정의한다.


핵심 요약

키워드 핵심 내용
목적 객체 생성 책임 분리 (사용 ↔ 생성 분리)
효과 결합도↓ 유연성↑ 유지보수성↑
DI 관계 생성 주체를 분리/위임한다는 면에서 DI 철학과 이어짐
확장 Spring Bean Factory, Abstract Factory, Builder로 확장 가능

전략(Strategy) 패턴

객체의 행위(알고리즘)를 캡슐화하여 외부에서 갈아끼울 수 있게 하는 패턴

코드를 직접 수정하지 않고 전략만 교체함으로써 행위 변경을 가능하게 한다.


왜 쓰는가

  • if/else, switch 등 분기 로직이 서비스 코드에 박혀 있으면
    • 코드 복잡도 상승
    • 변경 시 기존 코드 수정 필요 → OCP 위배
  • 전략 패턴은 “행동을 클래스로 분리”하여
    • 전략 교체 = 행위 변경
    • 컨텍스트는 전략이 무엇인지 모른 채 위임

구성 요소

구성 설명
Strategy (인터페이스) 공통 알고리즘 규약 선언
ConcreteStrategy 각각의 알고리즘 구현체
Context 전략을 사용하고 호출만 하는 쪽 (전략 내부 구현 모름)

팩토리 패턴과의 차이 정리

항목 전략 패턴 팩토리 패턴
목적 행위(알고리즘) 변경/교체 객체 생성 책임 분리
적용 시점 실행 중 전략을 갈아끼움 (Runtime 교체) 생성 시점에서 어떤 객체 만들지 결정
바꾸는 대상 알고리즘(행위) 인스턴스(구현체)
관련 원칙 OCP(행위 변경 시 코드 수정 X) DIP(구현에 의존하지 않음)
예시 결제 수단 전략 / 정렬 방식 교체 DB 커넥터 선택 / 엔진별 생성

쉽게 말해서

팩토리는 “무엇을 만들지”를 바꾸게 하는 패턴

전략은 “어떻게 동작할지”를 바꾸게 하는 패턴

둘은 동시에 조합해서도 많이 사용된다.

(예: 전략 객체를 Factory로 생성 → Context에 주입)


핵심 요약

  • 전략 패턴은 “행위 교체”를 위한 OCP 친화적 설계
  • 팩토리는 “생성 책임 분리”를 위한 DIP 기반 설계
  • 둘 다 확장에는 열려 있고 변경에는 닫혀 있게 만드는 패턴이다.

옵저버(Observer) 패턴

특정 객체(Subject)의 상태 변화를 구독(Subscribe) 해두었다가

변화가 발생하면 등록된 옵저버들에게 자동으로 알림을 보내는 패턴


특징

  • 발행-구독(Pub-Sub) 구조
  • Subject는 상태 변경 사실만 알리고, Observer는 그 알림을 받아 자체 로직 수행
  • 양방향 의존도를 낮춘 비동기 이벤트 기반 설계 가능

예시

  • 트위터 / 인스타 — 팔로우한 사람의 새 글 업로드 알림
  • 주식 앱 — 특정 종목 가격 변동 이벤트 감지
  • 이벤트 리스너 — 클릭 시 콜백 실행

왜 쓰는가

  • 상태 변경에 민감한 여러 모듈에게 동기화 필요
  • 상태 변경 감지를 “폴링(polling)” 하지 않고 이벤트 기반 처리
    • 폴링 : 변화 여부를 확인하기 위해 일정 간격으로 반복적으로 상태를 조회하는 방식
  • 확장에 유리 — 옵저버 추가/제거가 독립적

프록시(Proxy) 패턴

실제 객체(Real Subject)에 접근하기 전, 중간 계층이 요청을 가로채어 필터링·검증·변환·캐싱 등의 추가 작업을 수행하는 패턴


특징

  • 원본 객체에 직접 접근하지 않고 대리 객체를 통해 간접 접근
  • 접근 제어, 성능 최적화, 로깅, 보안 등 부가 기능을 삽입할 수 있음
  • 원본 객체 변경 없이 기능 확장 가능 (OCP 친화적)

활용 사례

  • 보안/권한 체크 — 접근 허용/거부 판단
  • 캐싱 — 반복 요청에 빠른 응답
  • 로깅 — 요청 흐름 기록
  • 지연 로딩(Lazy Loading) — 실제 객체 생성 지연

실전 예시: 프록시 서버

  • Nginx
    • Reverse Proxy 역할
    • 부하 분산, 보안 강화, Node.js 과부하 방지
  • Cloudflare
    • CDN + Reverse Proxy
    • DDoS 방어, HTTPS 인증, 속도 향상
  • 프론트 ↔ 백 간 CORS 해결용 Proxy
    • 브라우저 제한 우회

이터레이터(Iterator) 패턴

컬렉션 내부 구조를 몰라도, 동일한 인터페이스(Iterator)를 통해 일관된 방식으로 요소를 순회할 수 있게 하는 패턴


특징

  • 자료구조(Stack, List, Set, Tree 등)가 달라도 순회 방법을 통일
  • 내부 구조(인덱스/포인터 등)를 외부로 노출하지 않음 (캡슐화 유지)
  • 순회 로직을 별도 객체로 분리 — 컬렉션과 책임 분리

예시 (Java)

  • for (Element e : list) { ... } — 내부적으로 Iterator 사용
  • list.iterator().hasNext() / next()

노출 모듈(Revealing Module) 패턴

즉시 실행 함수(IIFE)를 사용해 내부 변수/함수를 private로 감추고, 외부에 노출하고 싶은 API만 public으로 반환하는 방식으로 접근 제어를 흉내내는 패턴


왜 필요한가

  • JavaScript에는 원래 private / public 키워드가 없었음
  • 외부에서 직접 접근하면 안 되는 내부 상태를 보호할 방법 필요
  • 클로저 + IIFE 조합을 통해 “은닉 + 선택적 노출” 구현

예시

const counterModule = (function () {
  let count = 0; // private

  function increase() {
    count++;
  }

  function get() {
    return count;
  }

  return {
    increase, // public
    get       // public
  };
})();

counterModule.increase();
console.log(counterModule.get()); // 1

MVC / MVP / MVVM 패턴 비교


MVC (Model–View–Controller)

애플리케이션을 Model / View / Controller 로 분리하여 책임을 나누는 대표적인 아키텍처 패턴

✅ 장점

  • 관심사 분리로 개발 단계별 집중 가능 (역할 명확)
  • 재사용성·확장성 용이

⚠️ 단점

  • 복잡해질수록 Model ↔ View 간 의존/관계가 얽히기 쉬움

구성

역할 설명
Model 데이터(DB, 상태 값 등), 비즈니스 로직 담당
View 화면(UI), 사용자에게 상태 표현, 변경 시 Controller에 통보
Controller 이벤트 처리·흐름 제어, Model↔View 연결자 역할

MVP (Model–View–Presenter)

MVC의 Controller 역할이 Presenter로 대체된 패턴

MVC와의 차이

항목 MVC의 Controller MVP의 Presenter
View 접근 View를 직접 조작하기도 함 View를 인터페이스로만 참조
결합도 View 의존도 상대적으로 높음 뷰를 추상화하여 결합도 낮춤

MVVM (Model–View–ViewModel)

Controller가 ViewModel 로 대체된 패턴

ViewModel은 View를 더 높은 수준으로 추상화한 계층

핵심 특징

  • Command
    • UI 동작들을 하나의 액션으로 캡슐화하여 View가 직접 로직을 가지지 않도록 함
    • 예: 버튼 클릭 이벤트를 saveCommand 로 바인딩하여 ViewModel이 처리
  • Data Binding
    • ViewModel 상태 ↔ View 가 자동 동기화
    • ViewModel 값을 바꾸면 View가 자동 업데이트

장점

  • UI를 코드 수정 없이 재사용 가능
  • 테스트 용이 (View와 로직 분리)

요약 비교

패턴 핵심 포인트 View 의존성 테스트 용이성
MVC 역할 분리의 기초형 비교적 높음 중간
MVP Presenter로 결합도 감소 낮음 높음
MVVM Data Binding/Command 기반 매우 낮음 매우 높음

results matching ""

    No results matching ""