CS 면접 바이블: Chapter 1 (3)

정지인·2026년 8월 10일
post-thumbnail

1.1.3 전략 패턴

  • 객체의 행위를 직접 수정하지 않고, 행위를 전략(Strategy) 객체로 분리하여 교체하는 디자인 패턴
  • 여러 알고리즘을 하나의 인터페이스로 캡슐화하여 상황에 따라 원하는 전략 선택 가능
  • 정책 패턴(Policy Pattern)이라고도 함

핵심 구조

  • Strategy: 공통 동작을 정의하는 인터페이스
  • ConcreteStrategy: 실제 동작을 구현한 구체적인 전략
  • Context: 전략을 전달받아 해당 전략의 동작을 실행하는 객체

예시: 결제 방식

결제 기능 자체는 동일하지만 결제 방식만 변경하는 경우

ShoppingCart (Context)
        ↓
PaymentStrategy (Strategy)
       ↙      ↘
LUNACard    KAKAOCard
  • PaymentStrategy → 결제 전략 인터페이스
  • LUNACardStrategy → LUNACard 결제 전략
  • KAKAOCardStrategy → KAKAOCard 결제 전략
  • ShoppingCart → 선택된 결제 전략을 사용하는 Context

예시 코드

import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.List;

// 1. 전략 인터페이스
interface PaymentStrategy {
    void pay(int amount);
}

// 2-1. 구체적인 전략 1: KAKAOCard
class KAKAOCardStrategy implements PaymentStrategy {
    private String name;
    private String cardNumber;
    private String cvv;
    private String dateOfExpiry;

    public KAKAOCardStrategy(String nm, String ccNum, String cvv, String expiryDate) {
        this.name = nm;
        this.cardNumber = ccNum;
        this.cvv = cvv;
        this.dateOfExpiry = expiryDate;
    }

    @Override
    public void pay(int amount) {
        System.out.println(amount + " paid using KAKAOCard.");
    }
}

// 2-2. 구체적인 전략 2: LUNACard
class LUNACardStrategy implements PaymentStrategy {
    private String emailId;
    private String password;

    public LUNACardStrategy(String email, String pwd) {
        this.emailId = email;
        this.password = pwd;
    }

    @Override
    public void pay(int amount) {
        System.out.println(amount + " paid using LUNACard.");
    }
}

// 3. 상품(Item) 클래스
class Item {
    private String name;
    private int price;

    public Item(String name, int cost) {
        this.name = name;
        this.price = cost;
    }

    public String getName() {
        return name;
    }

    public int getPrice() {
        return price;
    }
}

// 4. 쇼핑카트 (컨텍스트) 클래스
class ShoppingCart {
    List<Item> items;

    public ShoppingCart() {
        this.items = new ArrayList<Item>();
    }

    public void addItem(Item item) {
        this.items.add(item);
    }

    public void removeItem(Item item) {
        this.items.remove(item);
    }

    public int calculateTotal() {
        int sum = 0;
        for (Item item : items) {
            sum += item.getPrice();
        }
        return sum;
    }

    // 전략 객체를 매개변수로 받아 결제를 수행
    public void pay(PaymentStrategy paymentMethod) {
        int amount = calculateTotal();
        paymentMethod.pay(amount);
    }
}

// 5. 실행 클래스
public class HelloWorld {
    public static void main(String[] args) {
        ShoppingCart cart = new ShoppingCart();

        Item A = new Item("kundolA", 100);
        Item B = new Item("kundolB", 300);

        cart.addItem(A);
        cart.addItem(B);

        // LUNACard로 결제 (100 + 300 = 400원)
        cart.pay(new LUNACardStrategy("kundol@example.com", "pukubababo"));

        // KAKAOCard로 결제 (100 + 300 = 400원)
        cart.pay(new KAKAOCardStrategy("Ju hongchul", "123456789", "123", "12/01"));
    }
}

동작 흐름

cart.pay(new LUNACardStrategy(...));
cart.pay(new KAKAOCardStrategy(...));

ShoppingCart의 결제 로직을 변경하지 않고 전달하는 전략 객체만 변경하여 다른 결제 방식 사용

실행 결과

400 paid using LUNACard.
400 paid using KAKAOCard.

컨텍스트(Context)
특정 작업을 수행하는 데 필요한 상황이나 관련 정보를 의미하며, 전략 패턴에서는 전략 객체를 사용하여 실제 작업을 수행하는 객체를 의미함.

한 줄 정리

변하는 행위를 전략으로 분리하여, Context의 코드를 수정하지 않고 전략만 교체할 수 있도록 하는 패턴


Passport의 전략 패턴

  • Passport는 Node.js에서 인증 기능을 구현할 때 사용하는 미들웨어 라이브러리
  • 인증 방식을 각각 하나의 전략(Strategy) 으로 분리하여 필요한 인증 방식 선택 가능

대표적인 인증 전략

  1. LocalStrategy
  • 서비스 자체 회원가입 기반 인증
  • 아이디와 비밀번호를 이용한 인증
  1. OAuth Strategy
  • 외부 서비스 계정을 이용한 인증
  • ex) Google, Facebook, Naver 등

즉, 인증 로직 자체를 변경하지 않고 사용할 인증 전략만 교체하는 구조

예시 코드

var passport = require('passport')
  , LocalStrategy = require('passport-local').Strategy;

passport.use(new LocalStrategy(
  function(username, password, done) {
    User.findOne({ username: username }, function(err, user) {
      if (err) { return done(err); }
      if (!user) {
        return done(null, false, { message: 'Incorrect username.' });
      }
      if (!user.validPassword(password)) {
        return done(null, false, { message: 'Incorrect password.' });
      }
      return done(null, user);
    });
  }
));

핵심

Passport는 인증 방법을 각각 전략으로 분리하여 LocalStrategy, OAuth 전략 등을 상황에 따라 선택할 수 있음.

1.1.4 옵저버 패턴

  • 어떤 객체의 상태 변화를 관찰하다가 변화가 발생하면 관련 객체들에게 자동으로 알리는 디자인 패턴
  • 하나의 객체 변화가 여러 객체에 영향을 주어야 할 때 사용

구성 요소

  • Subject(주체): 상태가 변경되는 객체
  • Observer(옵저버): 주체의 상태 변화를 전달받는 객체
Subject
   │
   │ 상태 변경
   ↓
notify()
   │
   ├──→ Observer A
   ├──→ Observer B
   └──→ Observer C

예시: SNS 팔로우

어떤 사용자를 팔로우한 상황

사용자(Subject)
     │
     │ 게시물 업로드
     ↓
   알림
  ↙  ↓  ↘
팔로워 A  B  C
  • 사용자가 게시물을 등록하면 상태 변화 발생
  • 해당 사용자를 팔로우하고 있는 팔로워들에게 알림 전달
  • 팔로워들은 알림을 받은 후 각각 필요한 동작 수행

Subject와 Observer를 반드시 별도의 객체로 명확히 구분해야 하는 것은 아니며, 구현 방식에 따라 상태를 가진 객체가 직접 옵저버들을 관리할 수도 있음.

옵저버 패턴과 MVC

옵저버 패턴은 이벤트 기반 시스템이나 MVC와 같은 구조에서 활용될 수 있음

Model
  │
  │ 상태 변경
  ↓
notify / update
  │
  ↓
View
  • Model의 데이터 변경
  • 등록된 View에 변경 사실 전달
  • View가 새로운 상태를 기반으로 화면 갱신

※ MVC 전체가 옵저버 패턴인 것은 아니며, Model의 변경을 View에 전달하는 부분에서 옵저버 패턴을 활용할 수 있음

한 줄 정리

한 객체의 상태가 변하면 해당 객체를 관찰하고 있는 여러 객체에게 자동으로 변화를 전달하는 패턴

예시 코드

자바에서의 옵저버 패턴

interface Subject {
    void register(Observer obj);
    void unregister(Observer obj);
    void notifyObservers();
    Object getUpdate(Observer obj);
}

interface Observer {
    void update();
}
  • Subject → 옵저버 등록·삭제·알림을 담당하는 주체 인터페이스
  • Observer → 상태 변경 알림을 받기 위한 인터페이스
  • update() → 주체의 상태가 변경되었을 때 옵저버가 실행하는 메서드
class Topic implements Subject {
    private List<Observer> observers;
    private String message;

    @Override
    public void register(Observer obj) {
        if (!observers.contains(obj))
            observers.add(obj);
    }

    @Override
    public void unregister(Observer obj) {
        observers.remove(obj);
    }

    @Override
    public void notifyObservers() {
        observers.forEach(Observer::update);
    }

    @Override
    public Object getUpdate(Observer obj) {
        return this.message;
    }

    public void postMessage(String msg) {
        this.message = msg;
        notifyObservers();
    }
}
  • Topic → 상태가 변경되는 Subject
  • observers → 현재 등록된 옵저버 목록
  • register() → 옵저버 등록
  • unregister() → 옵저버 제거
  • notifyObservers() → 모든 옵저버의 update() 호출
  • postMessage() → 새로운 메시지 저장 후 모든 옵저버에게 변경 알림
class TopicSubscriber implements Observer {
    private String name;
    private Subject topic;

    @Override
    public void update() {
        String msg = (String) topic.getUpdate(this);
        System.out.println(name + ":: got message >> " + msg);
    }
}
  • TopicSubscriberObserver를 구현한 실제 구독자
  • update() 호출 시 Topic의 최신 메시지 조회

실행 흐름

Topic topic = new Topic();

Observer a = new TopicSubscriber("a", topic);
Observer b = new TopicSubscriber("b", topic);
Observer c = new TopicSubscriber("c", topic);

topic.register(a);
topic.register(b);
topic.register(c);

topic.postMessage("amumu is op champion!!");
Topic에 메시지 등록
        ↓
notifyObservers()
        ↓
 ┌──────┼──────┐
 ↓      ↓      ↓
 a      b      c
update update update
  • a, b, cTopic의 옵저버로 등록
  • Topic에 새로운 메시지 발생
  • notifyObservers() 실행
  • 등록된 모든 옵저버의 update() 호출
  • 각 옵저버가 변경된 메시지 확인

핵심: Topic의 상태가 변경되면 등록된 모든 TopicSubscriber에게 자동으로 변경 사실 전달


상속과 구현

상속 extends

  • 부모 클래스의 변수·메서드를 자식 클래스가 물려받아 사용하는 방식
  • 일반 클래스 또는 추상 클래스 기반
  • 기존 기능을 재사용하면서 추가 기능 확장 가능
class Child extends Parent {
}

구현 implements

  • 인터페이스에서 정의한 메서드를 클래스가 직접 구현하는 방식
  • 인터페이스는 "어떤 기능을 해야 하는지"에 대한 규칙 제공
  • 구현 클래스는 해당 메서드의 실제 동작 정의
class Topic implements Subject {
}

옵저버 예제에서는 Topic implements Subject, TopicSubscriber implements Observer 형태로 인터페이스를 구현


자바스크립트에서의 옵저버 패턴

  • 자바스크립트에서는 Proxy 객체를 이용해 객체의 변화를 감지하는 방식으로 구현 가능
  • 객체의 속성 조회·변경 등의 동작을 중간에서 가로채어 추가 로직 수행 가능

Proxy 객체

const p = new Proxy(target, handler);
  • target → Proxy가 감싸는 원본 객체
  • handler → 원본 객체의 동작을 가로챘을 때 수행할 로직

주요 트랩(Trap)

  • get() → 속성 조회 가로채기
  • set() → 속성 변경 가로채기
  • has()in 연산자 사용 가로채기

예를 들어,

const handler = {
    get: function(target, name) {
        return name === 'name'
            ? `${target.a} ${target.b}`
            : target[name];
    }
};

const p = new Proxy(
    { a: 'KUNDOL', b: 'IS AMUMU ZANGIN' },
    handler
);

console.log(p.name);
KUNDOL IS AMUMU ZANGIN
  • 실제 객체에는 name 속성이 존재하지 않음
  • p.name 접근 발생
  • Proxy의 get()이 접근을 가로챔
  • a, b 값을 이용해 새로운 값 반환

Proxy 객체를 이용한 옵저버 패턴

function createReactiveObject(target, callback) {
    const proxy = new Proxy(target, {
        set(obj, prop, value) {
            if (value !== obj[prop]) {
                const prev = obj[prop];
                obj[prop] = value;

                callback(
                    `${prop}가 [${prev}] >> [${value}] 로 변경되었습니다.`
                );
            }
            return true;
        }
    });

    return proxy;
}

사용 예시

const a = {
    "형규": "솔로"
};

const b = createReactiveObject(a, console.log);

b.형규 = "솔로";
b.형규 = "커플";
형규가 [솔로] >> [커플] 로 변경되었습니다.

동작 흐름

b.형규 값 변경
      ↓
Proxy의 set() 호출
      ↓
기존 값과 새로운 값 비교
      ↓
값이 변경된 경우 callback 실행
      ↓
변경 사실 전달
  • 동일한 값을 다시 대입 → 별도 동작 없음
  • 다른 값을 대입 → set()이 변경 감지
  • 변경 발생 시 콜백 함수를 통해 알림

옵저버 패턴과의 연결: 객체의 상태 변화를 감지하고, 변화가 발생했을 때 등록된 동작을 실행한다는 점에서 옵저버 패턴과 유사한 구조


Vue.js 3.0의 옵저버 패턴

  • Vue.js 3의 반응형 시스템에서 Proxy를 활용하여 객체의 상태 변화를 감지
  • reactive() 등으로 생성한 반응형 데이터가 변경되면 관련된 화면도 자동 갱신
데이터 변경
   ↓
Proxy가 변경 감지
   ↓
관련 반응형 효과 실행
   ↓
DOM 갱신
  • get() → 어떤 값이 사용되는지 추적하는 데 활용
  • set() → 값의 변경 감지에 활용
  • 데이터와 화면을 연결하여 상태 변경 → 화면 갱신 구조 구현

DOM(Document Object Model)
웹 페이지를 구성하는 HTML 요소들을 객체 형태로 표현한 구조

※ 정확히 말하면 Vue 3의 반응성 시스템 전체를 단순한 GoF 옵저버 패턴 하나로만 설명하기는 어렵고, Proxy를 통한 변경 감지와 의존성 추적·효과 실행이 결합된 반응형 시스템으로 이해하는 것이 더 정확함.


한 줄 정리

옵저버 패턴은 Subject의 상태 변화를 감지하여 관련 Observer들에게 자동으로 알리고, Java에서는 인터페이스와 옵저버 목록으로, JavaScript에서는 Proxy 등을 활용해 비슷한 반응형 구조를 구현할 수 있음.

profile
인천대학교 멋쟁이사자 13기 백엔드 & 14기 백엔트 파트장 및 부회장

0개의 댓글