[디자인 패턴] 전략패턴

변진상·2024년 1월 16일
0

학습 기록

목록 보기
5/31

이 글은 면접을 위한 CS 전공지식노트의 책을 읽고 학습 후 스터디 공유를 위한 글입니다.

전략패턴(Strategy Pattern)

전략패턴이란?

정책패턴(Policy Pattern)이라고 부르기도 하며, **객체의 행위를 바꾸고 싶은 경우** ‘직접’ 수정하지 않고 **전략이라고 부르는 ‘캡슐화한 알고리즘’을 컨텍스트 안에서 바꿔주면서** 상호 교체가 가능하게 만드는 패턴

💡 컨텍스트? 상황, 맥락, 문맥을 의미하며 개발자가 어떠한 작업을 완료하는 데 필요한 모든 관련 정보.

구현 코드

아래 코드는 물건을 담고 총액을 합산한 후 결제를 하는 시나리오다.

결제수단에 따른 전략을 각각의 클래스에 따로 추상화, 캡슐화 해놓고 결제를 하는 행위를 바꾸는 경우 그 캡슐화 한 알고리즘을 컨텍스트 안에서 바꿔준다.

interface PaymentStrategy {
  pay(amount: number): void;
}

class KAKAOCardStrategy implements PaymentStrategy {
// 결제 수단에 따른 결제에 관련된 행위 즉, 전략을 구현한 클래스
  private name: string;
  private cardNumber: string;
  private cvv: string;
  private dateOfExpiry: string;

  constructor(nm: string, ccNum: string, cvv: string, expiryDate: string) {
    this.name = nm;
    this.cardNumber = ccNum;
    this.cvv = cvv;
    this.dateOfExpiry = expiryDate;
  }

  public pay(amount: number): void {
    console.log(amount + ' paid using KAKAOCard.');
  }
}

class LUNACardStrategy implements PaymentStrategy {
// 결제 수단에 따른 결제에 관련된 행위 즉, 전략을 구현한 클래스
  private emailId: string;
  private password: string;

  constructor(email: string, pwd: string) {
    this.emailId = email;
    this.password = pwd;
  }

  public pay(amount: number): void {
    console.log(amount + ' paid using LUNACard.');
  }
}

class Item {
  private name: string;
  private price: number;

  constructor(name: string, cost: number) {
    this.name = name;
    this.price = cost;
  }

  public getName(): string {
    return this.name;
  }

  public getPrice(): number {
    return this.price;
  }
}

class ShoppingCart {
  private items: Array<Item>;

  constructor() {
    this.items = new Array<Item>();
  }

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

  public calculateTotal(): number {
    const sum = this.items.reduce(
      (total, crntItem) => total + crntItem.getPrice(),
      0
    );

    return sum;
  }

  public pay(paymentMethod: PaymentStrategy){
    let amount = this.calculateTotal();
    paymentMethod.pay(amount);
  }
}

let cart = new ShoppingCart();

let itemA = new Item("MacBook Pro", 300);
let itemB = new Item("MacBook Amateur", 150);

cart.addItem(itemA);
cart.addItem(itemB);

// pay by KAKAOCard
cart.pay(new KAKAOCardStrategy('변진상', "123123123", "123", "12/05"));
// => 450 paid using KAKAOCard.

// payby LUNACard
cart.pay(new LUNACardStrategy("진상@진상.진상", "1231231223"));
// => 450 paid using LUNACard.

// ShoppingCart 클래스의 인스턴스 내부 콘텍스트에 전략을 캡슐화한 인스턴스를 전달해 그 행위를 바꾼다.

전략패턴이 사용되고 있는 라이브러리 - passport

Node.js 인증모듈 구현을 위한 미들웨어 라이브러리 → passport

서비스 내의 회원 가입된 아이디와 비밀번호를 이용해 인증하는 LocalStrategy 전략과 페이스북, 네이버 등 외부 서비스를 기반으로 인증하는 OAuth 전략 등을 지원.

다음 코드에서 메서드에 전략을 매개 변수로 넣어 전략만 바꿔 인증하는 것을 확인할 수 있다.

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);
        });
    }
));

전략패턴의 장점

  • 유연성과 확장성: 전략 패턴은 알고리즘을 독립적으로 캡슐화하므로 알고리즘을 쉽게 추가하거나 교체할 수 있다.
  • 유지보수 용이성: 각각의 전략이 독립적으로 구현되어 있기 때문에, 특정 전략을 수정하거나 개선할 때 해당 전략만 변경하면 된다. 다른 전략과 전략이 전달되는 컨텍스트에 영향을 미치지 않아 유지보수가 용이하다.
  • 코드 재사용: 전략 패턴을 사용하면 동일한 전략을 여러 곳에서 재사용할 수 있다. 동일한 알고리즘이 다양한 컨텍스트에서 사용될 때 중복 코드를 방지할 수 있다.
  • 컨텍스트와 전략의 독립성: 컨텍스트의 변경이 전략에 영향을 미치지 않고, 반대로 전략의 변경이 컨텍스트에 영향을 미치지 않는다.

전략패턴의 단점

  • 복잡성 증가: 클래스의 수가 증가, 간결한 알고리즘의 경우에는 전략 패턴을 도입하기 위한 비용이 과도할 수 있다.
  • 사용자의 이해와 학습: 전략 패턴을 이해하고 사용하려면 일정한 학습 필요. 팀 내의 일관성 있는 사용을 유지하고 설명하는 비용 발생.
  • 런타임 오버헤드: 전략을 교체하는 데 동적으로 객체를 생성하는 경우, 런타임 오버헤드가 발생할 수 있다.

전략 패턴은 알고리즘의 동적인 교체와 다양한 변형이 필요한 경우에 유용

profile
자신을 개발하는 개발자!

0개의 댓글