전략적 알고리즘 교체
장거리 여행을 계획할 때, 우리는 다양한 이동 수단 중에서 선택할 수 있습니다. 비행기, 기차, 자동차, 배 등 각 교통수단은 저마다 다른 속도, 비용, 편의성을 제공합니다. 상황과 필요에 따라 적합한 교통수단을 선택하는 것처럼, 소프트웨어 역시 다양한 전략 중에서 유연하게 선택할 수 있어야 합니다.
'스트래티지(Strategy)'라는 용어는 '전략'을 의미하며, 이는 본질적으로 전략적 패턴을 나타냅니다. 전쟁이나 첩보 작전을 수립할 때와 마찬가지로, 하나의 목표를 달성하기 위한 여러 경로가 존재할 수 있다는 개념입니다.
1980년대 Smalltalk 언어 개발자들 사이에서는 행동을 객체로 표현하는 개념이 널리 활용되었습니다. Smalltalk는 '블록(blocks)'이라는 개념을 통해 코드를 일급 객체(first-class objects)로 취급할 수 있었고, 이를 통해 다양한 전략을 손쉽게 교체할 수 있었습니다.
또한 Kent Beck과 Ward Cunningham이 개발한 초기 디자인 패턴에도 스트래티지 패턴과 유사한 개념들이 존재했습니다. 특히 그들의 "CHECKS" 패턴 언어(1987-1988년경)에는 다양한 유효성 검사 알고리즘을 교체 가능하게 만드는 패턴이 포함되어 있었습니다. 이후 1994년 Gang of Four(GoF)에 의해 스트래티지 패턴으로 공식 문서화되었습니다.
온라인 쇼핑몰의 결제 시스템을 예로 들면, 주문 시 고객은 신용카드, 페이, 모바일 간편결제 등 다양한 결제 수단을 선택할 수 있습니다.
// 전략 인터페이스
interface PaymentStrategy {
boolean pay(double amount);
}
// 구체적 전략 1: 신용카드 결제
class CreditCardStrategy implements PaymentStrategy {
private String name;
private String cardNumber;
private String cvv;
private String dateOfExpiry;
public CreditCardStrategy(String name, String cardNumber, String cvv, String dateOfExpiry) {
this.name = name;
this.cardNumber = cardNumber;
this.cvv = cvv;
this.dateOfExpiry = dateOfExpiry;
}
@Override
public boolean pay(double amount) {
System.out.println(amount + "원을 신용카드로 결제했습니다.");
// 실제로는 여기서 신용카드 결제 처리 로직 수행
return true;
}
}
// 구체적 전략 2: 페이 결제
class PayStrategy implements PaymentStrategy {
private String email;
private String password;
public PayStrategy(String email, String password) {
this.email = email;
this.password = password;
}
@Override
public boolean pay(double amount) {
System.out.println(amount + "원을 페이로 결제했습니다.");
// 실제로는 여기서 페이팔 결제 처리 로직 수행
return true;
}
}
// 구체적 전략 3: 모바일 결제
class MobilePaymentStrategy implements PaymentStrategy {
private String phoneNumber;
private String pin;
public MobilePaymentStrategy(String phoneNumber, String pin) {
this.phoneNumber = phoneNumber;
this.pin = pin;
}
@Override
public boolean pay(double amount) {
System.out.println(amount + "원을 모바일 결제로 처리했습니다.");
// 실제로는 여기서 모바일 결제 처리 로직 수행
return true;
}
}
// 컨텍스트: 쇼핑 카트
class ShoppingCart {
private List<Item> items;
public ShoppingCart() {
items = new ArrayList<>();
}
// 장바구니에 상품 넣기
public void addItem(Item item) {
items.add(item);
}
// 장바구니에서 상품 빼기
public void removeItem(Item item) {
items.remove(item);
}
// 장바구니 상품의 총 금액 계산
public double calculateTotal() {
return items.stream().mapToDouble(Item::getPrice).sum();
}
// 장바구니 상품 결제 : 다양한 결제 수단으로 결제 한다.
public boolean pay(PaymentStrategy paymentStrategy) {
double amount = calculateTotal();
return paymentStrategy.pay(amount);
}
}
// 상품 클래스
class Item {
private String name;
private double price;
public Item(String name, double price) {
this.name = name;
this.price = price;
}
public String getName() {
return name;
}
public double getPrice() {
return price;
}
}
위와 같이 객체들을 정의했으면, 아래 코드와 같이 사용합니다.
public class ShoppingDemo {
public static void main(String[] args) {
// 쇼핑 카트 생성
ShoppingCart cart = new ShoppingCart();
// 아이템 추가
cart.addItem(new Item("노트북", 1500000));
cart.addItem(new Item("헤드폰", 150000));
cart.addItem(new Item("마우스", 50000));
// 총액 계산
System.out.println("총 결제 금액: " + cart.calculateTotal() + "원");
// 결제 방식 선택 및 결제
System.out.println("\n결제 방식 1: 신용카드");
cart.pay(new CreditCardStrategy("홍길동", "1234-5678-9101-1121", "123", "12/25"));
System.out.println("\n결제 방식 2: 페이");
cart.pay(new PayStrategy("hong@example.com", "password"));
System.out.println("\n결제 방식 3: 모바일 결제");
cart.pay(new MobilePaymentStrategy("010-1234-5678", "0000"));
}
}
코드들을 실행하면 다음과 같습니다.
총 결제 금액: 1700000.0원
결제 방식 1: 신용카드 1700000.0원을 신용카드로 결제했습니다.
결제 방식 2: 페이 1700000.0원을 페이로 결제했습니다.
결제 방식 3: 모바일 결제 1700000.0원을 모바일 결제로 처리했습니다.
'결제(pay)'라는 동일한 목표를 달성하기 위해 여러 효율적인 방법이 필요할 때 스트래티지 패턴이 효과적입니다. 단, 중요한 전제조건은 하나 이상의 공통점이 있어야 한다는 것입니다. '결제(pay)'라는 행위에 대한 스트래티지 패턴을 설계하면서 '리뷰 작성'과 같은 무관한 전략을 포함할 수는 없습니다.
경영학자 클레이튼 크리스텐슨은 "성공한 기업들이 실패하는 이유는 그들이 나쁜 결정을 내려서가 아니라, 한때 좋았던 결정에 너무 오래 집착하기 때문이다"라고 지적합니다. 또한 피터 드러커는 "효과적인 지식 노동자는 자신의 일과 상황에 맞게 작업 방식을 조정한다"라고 언급했습니다.
성공적인 시스템은 다양한 전략의 레퍼토리를 확보하고, 상황에 맞게 유연하게 전략을 선택하며, 지속적으로 그 전략들을 평가하고 개선하는 능력에 달려 있습니다.
고정된 알고리즘이나 생각에 집착하기보다는, 다양한 전략의 레퍼토리를 발전시키고, 상황에 맞게 지혜롭게 선택하며, 끊임없이 학습하고 적응하는 것이 살아남는 길입니다.