[Design Pattern] Strategy Pattern
전략패턴
객체의 행위를 바꾸고 싶은 경우 직접 수정하지 않고, 캡슐화한 알고리즘 즉, 전략을 컨텍스트 안에서 바꿔주면서 교체가 가능하도록 만드는 것이다.
기존방식이 하나의 함수 안에 A일 때는 a실행, B일 때는 b 실행이라고 명시적으로 모든 방법을 다 적어두는 것이라고 한다면, 전략 패턴은 A와 B를 각각 독립된 객체로 만들고, 상황에 따라 필요한 객체를 넘겨주는 방식이다.
전략 패턴을 왜 쓸까?
if-else문이 길게 늘어지고 복잡해지는 걸 방지하기 위해서이다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
const authentication = (strategy) => {
if(!strategy) {
return `error`
}
if(strategy === 'faceID' ) {
return `user choose faceID. process...`
} else if (strategy === 'fingerPrint' ) {
return `user choose fingerPrint. process...`
} else if( strategy === 'PIN' ) {
return `user choose PIN. process...`
}
}
console.log(authentication('faceID'))
console.log(authentication('fingerPrint'))
console.log(authentication('PIN'))
만약 여기서 인증방식이 추가된다면 else if로 기존 코드를 계속 수정해야 한다. 이는 ‘기능이 변하거나 확장되는 것은 가능하지만 그 과정에서 기존의 코드가 수정되지 않아야 한다’는OCP 원칙을 위반한다. 따라서 위의 코드를 전략 패턴이 적용된 코드로 수정하면 아래와 같다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
const faceID = (user) => `${user} choose faceID. process...`
const fingerPrint = (user) => `${user} choose fingerPrint. process...`
const PIN = (user) => `${user} choose PIN. process...`
const authentication = (user, strategy) => {
if(!strategy) {
return 'Error: Invalid authentication strategy'
}
if(!user) {
return 'Error: Invalid user'
}
return strategy(user);
}
console.log(authentication('Joan', faceID)) //Joan choose faceID. process...
console.log(authentication('Joan', fingerPrint))//Joan choose fingerPrint. process...
console.log(authentication('Joan', PIN)) //Joan choose PIN. process...
만약 금융인증서, 모바일 신분증 등 인증수단이 추가되어도 authentication()은 수정할 필요 없다.
실제 전략패턴을 사용하는 라이브러리 : Passport
Passport라는 인증 모듈을 구현할 때 사용하는 미들웨어 라이브러리에서 전략패턴이 사용된다는 걸 알 수 있다. 약 500가지 정도 되는 인증 strategies documentation을 보면
1
2
passport.use(new BearerStrategy(
...
1
2
passport.use(new TwitterStrategy({
...
1
2
passport.use(new GoogleStrategy({
...
모두 passport.use()라는 함수에 각 인증 전략 GoogleStrategy, TwitterStrategy, BearerStrategy 를 변수로 넣어서 로직을 수행하는 걸 볼 수 있다.
실제 passport.use()함수를 들여다보면,
1
2
3
4
5
6
7
8
9
10
Authenticator.prototype.use = function(name, strategy) {
if (!strategy) {
strategy = name;
name = strategy.name;
}
if (!name) { throw new Error('Authentication strategies must have a name'); }
this._strategies[name] = strategy;
return this;
};
이렇게 작성되어 있다.
마치며
결국 전략 패턴은 변경에는 닫혀 있고 확장에는 열려 있어야 한다는 OCP(개방-폐쇄 원칙)를 가장 직관적으로 실현하는 방법이다. 단순한 if-else문의 나열에서 벗어나 각각의 알고리즘을 독립된 객체로 분리함으로써, 코드의 가독성을 높이고 유지보수를 용이하게 만든다. Passport 라이브러리의 사례처럼 복잡한 인증 로직조차 단일한 인터페이스를 통해 유연하게 관리할 수 있다는 점은 전략 패턴이 실무에서 얼마나 강력한 도구가 되는지를 잘 보여준다.