1. 전략 패턴(Strategy Pattern)

서주홍's avatar
Aug 13, 2024
1. 전략 패턴(Strategy Pattern)
 
💡
전략 패턴은 다양한 알고리즘(혹은 행동)을 정의하고, 각각의 알고리즘을 별도의 클래스로 캡슐화하여 상호 교환이 가능하도록 하는 디자인 패턴입니다. 전력 패턴을 사용하면 클라이언트는 구체적인 알고리즘에 의존하지 않고, 추상적인 인터페이스를 통해 다양한 알고리즘을 사용할 수 있습니다.
 
  • 장점
    • 유연성: 코드의 유연성을 높여줍니다. 새로운 알고리즘이 추가되더라도 기존 코드를 수정할 필요 없이 쉽게 확장할 수 있습니다.
    • 유지보수성: 알고리즘을 개별 클래스로 분리하여, 각 알고리즘이 독립적으로 변경될 수 있어 유지보수가 용이해집니다.
  • 예시
    • 모자 장수의 재판 상황을 추상화하여 재판관, 진행자, 증인과 같은 역할로 분리하면, 새로운 캐릭터가 추가되더라도 기존 코드를 수정하지 않고 쉽게 확장할 수 있습니다.
    • attack() 메서드를 추상화하면, 어떤 캐릭터든 해당 메서드를 재정의하여 동적 바인딩을 통해 다양한 공격 방식을 구현할 수 있습니다.
 

■ 예시 코드1

// 역할 인터페이스 정의 interface Role { void performRole(); } // 재판관 역할 클래스 class Judge implements Role { @Override public void performRole() { System.out.println("재판관이 재판을 주재합니다."); } } // 진행자 역할 클래스 class Moderator implements Role { @Override public void performRole() { System.out.println("진행자가 재판을 진행합니다."); } } // 증인 역할 클래스 class Witness implements Role { @Override public void performRole() { System.out.println("증인이 증언을 합니다."); } } // 새로운 캐릭터 추가: 변호사 역할 클래스 class Lawyer implements Role { @Override public void performRole() { System.out.println("변호사가 변론을 합니다."); } } // 재판 클래스 class Court { // 역할을 수행하는 메서드 void startTrial(Role role) { role.performRole(); } } // 테스트 클래스 public class Test { public static void main(String[] args) { Court court = new Court(); // 재판 시스템에서 역할 수행 Role judge = new Judge(); Role moderator = new Moderator(); Role witness = new Witness(); Role lawyer = new Lawyer(); // 새로 추가된 변호사 역할 court.startTrial(judge); // "재판관이 재판을 주재합니다." court.startTrial(moderator); // "진행자가 재판을 진행합니다." court.startTrial(witness); // "증인이 증언을 합니다." court.startTrial(lawyer); // "변호사가 변론을 합니다." } }
 

■ 예시 코드2

// Character 클래스: 모든 캐릭터들이 상속받을 기본 클래스 class Character { // attack 메서드: 기본적으로 아무 동작도 하지 않는 공격 메서드 // 이 메서드는 자식 클래스에서 재정의(Override)될 예정입니다. void attack() {} } // Zilot 클래스: Character 클래스를 상속받아 공격 방식을 재정의한 클래스 class Zilot extends Character { @Override void attack() { // Zilot 캐릭터는 지상 공격을 수행합니다. System.out.println("지상 공격"); } } // Mutalisk 클래스: Character 클래스를 상속받아 공격 방식을 재정義한 클래스 class Mutalisk extends Character { @Override void attack() { // Mutalisk 캐릭터는 지상과 공중을 모두 공격할 수 있습니다. System.out.println("지상, 공중 공격"); } } // Medic 클래스: Character 클래스를 상속받아 공격 방식을 재정의한 클래스 class Medic extends Character { @Override void attack() { // Medic 캐릭터는 공격할 수 없기 때문에, 이 메서드에서는 공격 불가능을 출력합니다. System.out.println("공격을 못해.."); } } // Test 클래스: 프로그램의 실행을 담당하는 메인 클래스 public class Test { // 기능 메서드: Character 타입의 객체를 받아서 해당 객체의 attack 메서드를 호출하는 메서드 // 동적 바인딩을 통해 자식 클래스에서 재정의된 attack 메서드가 실행됩니다. static void 기능(Character character) { character.attack(); } // main 메서드: 프로그램의 진입점으로, 여러 캐릭터 객체를 생성하고 기능 메서드를 호출합니다. public static void main(String[] args) { // Zilot, Mutalisk, Medic 캐릭터 객체를 생성 Character z1 = new Zilot(); Character mu1 = new Mutalisk(); Character me1 = new Medic(); // 각 캐릭터의 공격 방식 실행 // 동적 바인딩에 의해 실제 객체의 타입에 맞는 attack 메서드가 호출됩니다. 기능(z1); // "지상 공격" 출력 기능(mu1); // "지상, 공중 공격" 출력 기능(me1); // "공격을 못해.." 출력 } }
 
 

■ 객체 지향 설계 원칙 (SOLID 원칙)

 
  • DIP는 고수준 모듈이 저수준 모듈에 의존하지 않도록 합니다.
  • 추상화된 인터페이스나 상위 클래스에 의존하도록 설계하는 원칙입니다.
  • 변화 가능성이 적은 추상적인 것에 의존하고,
  • 구체적인 것에 의존하지 않도록 하여 유연성과 유지보수성을 높입니다.
 

■ 의존 역전 원칙 (Dependency Inversion Principle, DIP)

  • 예시
    • static void 레이싱(Car car) 메서드에서 Car에 의존하며, Sonata와 같은 구체적인 클래스에 의존하지 않습니다.
    • 구체적인 클래스가 아닌 추상적인 클래스나 인터페이스에 의존하도록 함으로써 코드의 유연성을 확보합니다.
 
 

■ 개방-폐쇄 원칙 (Open-Closed Principle, OCP)

  • OCP는 소프트웨어 모듈은 확장에는 열려 있어야 하고
  • 수정에는 닫혀 있어야 한다는 원칙
  • 새로운 기능을 추가할 때 기존 코드를 수정하지 않고, 새로운 클래스를 추가함으로써 확장할 수 있어야 합니다.
 
  • 예시
    • instanceof를 사용하는 타입 검사는 OCP를 위반합니다. 레이싱(Car car) 메서드에서 instanceof를 사용하지 않고, 자식 클래스들이 메서드를 재정의(Override)하여 동작을 다르게 구현하도록 함으로써 OCP를 준수할 수 있습니다.
 
 

단일 책임 원칙 (Single Responsibility Principle, SRP)

  • SRP는 클래스는 하나의 책임만 가져야 하며
  • 그 책임은 하나의 변경 이유만 있어야 한다는 원칙입니다.
  • 책임을 명확히 분리함으로써 코드의 이해도와 유지보수성을 높일 수 있습니다.
 
  • 예시
    • 자동차 클래스에서 엔진은 엔진의 역할만 담당해야 하며,
    • 다른 역할은 별도의 클래스로 분리해야 합니다.
    • 책임을 분리하면, 각각의 클래스가 독립적으로 변경될 수 있어 코드가 더 명확하고 관리하기 쉬워집니다.
 

■ 예시 코드

// 결제 전략 인터페이스 (DIP 적용) interface PaymentStrategy { void pay(int amount); } // 구체적인 결제 전략들 (OCP 적용) class CreditCardPayment implements PaymentStrategy { @Override public void pay(int amount) { System.out.println(amount + "원을 신용카드로 결제합니다."); } } class PaypalPayment implements PaymentStrategy { @Override public void pay(int amount) { System.out.println(amount + "원을 페이팔로 결제합니다."); } } class BitcoinPayment implements PaymentStrategy { @Override public void pay(int amount) { System.out.println(amount + "원을 비트코인으로 결제합니다."); } } // 결제 처리 클래스 (SRP 적용) class PaymentProcessor { // 결제 전략을 필드로 보유 (DIP 적용) private PaymentStrategy paymentStrategy; // 결제 전략 설정 메서드 public void setPaymentStrategy(PaymentStrategy paymentStrategy) { this.paymentStrategy = paymentStrategy; } // 결제 처리 메서드 public void processPayment(int amount) { if (paymentStrategy != null) { paymentStrategy.pay(amount); } else { System.out.println("결제 전략이 설정되지 않았습니다."); } } } // 테스트 클래스 public class Test { public static void main(String[] args) { PaymentProcessor processor = new PaymentProcessor(); // 신용카드 결제 처리 processor.setPaymentStrategy(new CreditCardPayment()); processor.processPayment(10000); // "10000원을 신용카드로 결제합니다." // 페이팔 결제 처리 processor.setPaymentStrategy(new PaypalPayment()); processor.processPayment(20000); // "20000원을 페이팔로 결제합니다." // 비트코인 결제 처리 processor.setPaymentStrategy(new BitcoinPayment()); processor.processPayment(30000); // "30000원을 비트코인으로 결제합니다." } }
 
 

■ 전략 패턴의 의도

  • 전략 패턴의 목적은 다양한 알고리즘이나 행동들을 정의하고,
  • 이들을 별도의 클래스로 캡슐화한 후, 이들 클래스를 클라이언트 코드에서 상호 교환 가능하게 만드는 것입니다.
  • 이를 통해 클라이언트 코드는 구체적인 알고리즘에 의존하지 않고, 더 유연하게 설계할 수 있습니다.
 
💡
정리 사이트 참고
 
 
Share article

maestrojava