Strategy Pattern 에 대하여

지난 포스팅에서는 Singleton Pattern, Factory Pattern 에 대해서 알아봤습니다.
이번 포스팅은 아마 Node.js 환경에서 인증 파트를 구현해봤다면 들어봤을법한 전략패턴을 알아봅시다.
Strategy Pattern
전략패턴(혹은 정책 패턴, policy pattern)은 객체의 행위를 바꿀 때 직접 수정하는 것이 아닌, 전략이라 칭해지는 캡슐화된 알고리즘을 컨텍스트 내에서 바꿔 상호 교체가 가능하게끔 하는 패턴입니다.
말은 좀 어려운데, 예제를 들어보겠습니다.
자동자 경주 게임을 개발 중인데 처음엔 자동차의 주행방식이 "일반주행" 한 개 뿐이었습니다.
시간이 지나고 유저가 늘어가면서, 개발자는 재미를 위해 터보 부스트, 드리프트 주행등 다양한 주행 방식을 추가했습니다.
게임은 성공했지만, 새로운 주행 방식을 추가 구현할 때마다 주행 메인 클래스의 크기가 엄청나게 늘어나고, 간단한 버그 수정 조차 전체 클래스에 영향을 미쳐서 이미 잘 돌아가고 있던 코드에서 오류가 발생하는 상황입니다.
이 때, 전략패턴을 사용하면 다양한 주행 옵션들을 "전략" 클래스로 추출할 수 있습니다.
클라이언트(객체의 행위를 바꾸는 주체)가 원하는 전략을 컨텍스트에 전달합니다.
그렇게 하면 새로운 주행 방식을 추가해도, 기존 전략들에는 영향이 없게 되고,
기존 주행 방식을 수정한다해도 다른 전략들에 영향을 미치지 않게됩니다.
JavaScript 에서의 Strategy Pattern
위에서 예시로 든 경우를 코드로 살펴봅시다.
class DrivingStrategy {
drive() {
throw new Error("drive() 메서드를 구현해야합니다.");
}
}
class NormalDrive extends DrivingStrategy {
drive() {
console.log("일반 속도로 주행합니다.");
}
}
class TurboDrive extends DrivingStrategy {
drive() {
console.log("터보 부스트! 파이어~");
}
}
class DriftDrive extends DrivingStrategy {
drive() {
console.log("드리프트! 커브 돌아돌아");
}
}
class Car {
constructor(strategy) {
this.strategy = strategy;
}
setStrategy(strategy) {
this.strategy = strategy;
}
drive() {
this.strategy.drive();
}
}
const car = new Car(new NormalDrive()); // 기본 주행 전략: 일반 주행
car.drive(); // "일반 속도로 주행합니다."
car.setStrategy(new TurboDrive()); // 주행 전략 변경: 터보 부스트
car.drive(); // "터보 부스트! 파이어~"
car.setStrategy(new DriftDrive()); // 주행 전략 변경: 드리프트 주행
car.drive(); // "드리프트! 커브 돌아돌아"
setStrategy() 를 통해서 주행 전략을 쉽게 변경할 수 있습니다.
Car 클래스는 특정 주행 방식에 의존하지 않고, 전략 객체에 위임합니다.
만약 여기서 새로운 주행 방식이 추가된다고 하여도, DrivingStrategy 를 상속하여 구현하면 손쉽게 구현 가능합니다.
Passport 의 Strategy Pattern

passport 는 Node.js 에서 인증 모듈을 구현할 때 사용하는 미들웨어 라이브러리입니다.
아래와 같이 사용할 수 있습니다.
import passport from 'passport';
import { Strategy as LocalStrategy } from 'passport-local';
import User from './models/User.js';
passport.use(new LocalStrategy(
async (username, password, done) => {
try {
const user = await User.findOne({ username });
if (!user) {
return done(null, false, { message: 'Incorrect username.' });
}
const isValid = await user.validPassword(password);
if (!isValid) {
return done(null, false, { message: 'Incorrect password.' });
}
return done(null, user);
} catch (err) {
return done(err);
}
}
));
위 코드의 passport.use(new LocalStrategy(...)) 처럼
passport.use() 메서드에 전략을 매개변수로 넣어 로직을 수행합니다.
오늘 이렇게 전략패턴을 알아봤습니다.
전략패턴은 다양한 알고리즘을 유연하게 변경할 수 있도록 설계하는 디자인패턴입니다.
이를 잘 활용하면 코드의 유연성과 확장성 향상, 더 나아가 불필요한 조건문을 줄이고 더욱 객체지향적인 설계를 할 수 있습니다.
다음 포스팅은 옵저버 패턴에 대해서 알아보도록 하겠습니다.
질문이 있거나 잘못된 내용, 오탈자가 있다면 댓글 남겨주세요.