CodeGym /Java Blog /무작위의 /전략 디자인 패턴
John Squirrels
레벨 41
San Francisco

전략 디자인 패턴

무작위의 그룹에 게시되었습니다
안녕! 오늘 수업에서는 전략 패턴에 대해 이야기하겠습니다. 이전 수업에서 우리는 이미 상속의 개념에 대해 간략하게 알게 되었습니다. 잊어버린 경우를 대비하여 이 용어는 일반적인 프로그래밍 작업에 대한 표준 솔루션을 의미함을 상기시켜 드립니다. CodeGym에서는 거의 모든 질문에 대한 답을 Google에서 찾을 수 있다고 자주 말합니다. 당신의 과제가 무엇이든 이미 다른 누군가가 성공적으로 해결했을 것이기 때문입니다. 패턴은 가장 일반적인 작업에 대한 검증된 솔루션 또는 문제가 있는 상황을 해결하는 방법입니다. 이것들은 스스로 재창조할 필요가 없는 "바퀴"와 같지만 언제 어떻게 사용해야 하는지 알아야 합니다. :) 패턴의 또 다른 목적은 균일한 아키텍처를 촉진하는 것입니다. 다른 사람의 코드를 읽는 것은 쉬운 일이 아닙니다! 모두가 다른 코드를 작성합니다. 동일한 작업을 여러 가지 방법으로 해결할 수 있기 때문입니다. 그러나 패턴을 사용하면 여러 프로그래머가 코드의 각 줄을 파고들지 않고도 프로그래밍 논리를 이해할 수 있습니다(처음 보는 경우에도!). 오늘 우리는 "전략"이라는 가장 일반적인 디자인 패턴 중 하나를 살펴봅니다. 디자인 패턴: 전략 - 2Conveyance 개체와 능동적으로 작업할 프로그램을 작성한다고 상상해 보십시오. 우리 프로그램이 정확히 무엇을 하는지는 중요하지 않습니다. 하나의 Conveyance 부모 클래스와 세 개의 자식 클래스 Sedan , TruckF1Car 로 클래스 계층 구조를 만들었습니다 .

public class Conveyance {

   public void go() {
       System.out.println("Moving forward");
   }

   public void stop() {

       System.out.println("Braking!");
   }
}

public class Sedan extends Conveyance {
}

public class Truck extends Conveyance {
}

public class F1Car extends Conveyance {
}
세 개의 자식 클래스 모두 부모로부터 두 가지 표준 메서드인 go()stop() 을 상속합니다 . 우리의 프로그램은 매우 간단합니다. 우리 자동차는 앞으로 나아가고 브레이크를 밟을 수만 있습니다. 작업을 계속하면서 우리는 자동차에 새로운 메서드인 fill() ("주유 탱크 채우기"를 의미)을 제공하기로 결정했습니다. Conveyance 상위 클래스 에 추가했습니다 .

public class Conveyance {

   public void go() {
       System.out.println("Moving forward");
   }

   public void stop() {

       System.out.println("Braking!");
   }
  
   public void fill() {
       System.out.println("Refueling!");
   }
}
이렇게 간단한 상황에서 정말 문제가 발생할 수 있습니까? 사실 그들은 이미... 디자인 패턴: 전략 - 3

public class Stroller extends Conveyance {

   public void fill() {
      
       // Hmm... This is a stroller for children. It doesn't need to be refueled :/
   }
}
이제 우리 프로그램에는 일반적인 개념에 잘 맞지 않는 운송 수단(유모차)이 있습니다. 페달이 있거나 무선 조종이 될 수 있지만 한 가지 확실한 것은 가스를 주입할 곳이 없다는 것입니다. 우리의 클래스 계층 구조는 일반적인 메서드가 필요하지 않은 클래스에 의해 상속되도록 했습니다. 이 상황에서 우리는 무엇을 해야 합니까? 유모차에 연료를 보급하려고 할 때 아무 일도 일어나지 않도록 Stroller 클래스 fill() 메서드를 재정의할 수 있습니다 .

public class Stroller extends Conveyance {

   @Override
   public void fill() {
       System.out.println("A stroller cannot be refueled!");
   }
}
그러나 이것은 중복 코드 외에 다른 이유가 없다면 성공적인 솔루션이라고 할 수 없습니다. 예를 들어 대부분의 클래스는 부모 클래스의 메서드를 사용하지만 나머지는 강제로 재정의해야 합니다. 15개의 클래스가 있고 그 중 5-6개의 동작을 재정의해야 하는 경우 코드 중복이 매우 광범위해집니다. 인터페이스가 우리를 도울 수 있을까요? 예를 들면 다음과 같습니다.

public interface Fillable {
  
   public void fill();
}
하나의 fill() 메서드로 Fillable 인터페이스를 생성합니다 . 그러면 급유가 필요한 운송 수단은 이 인터페이스를 구현하지만 다른 운송 수단(예: 유모차)은 구현하지 않습니다. 그러나이 옵션은 우리에게 적합하지 않습니다. 미래에는 클래스 계층 구조가 매우 커질 수 있습니다(세상에 얼마나 많은 다양한 유형의 운송 수단이 있는지 상상해 보십시오). fill() 을 재정의하고 싶지 않기 때문에 상속과 관련된 이전 버전을 포기했습니다.여러 번 방법. 이제 모든 클래스에서 구현해야 합니다! 그리고 만약 우리가 50개라면? 그리고 프로그램이 자주 변경된다면(실제 프로그램에서는 거의 항상 그렇습니다!) 50개 클래스를 모두 돌진하고 각 클래스의 동작을 수동으로 변경해야 합니다. 그렇다면 결국 이런 상황에서 어떻게 해야 할까요? 문제를 해결하기 위해 다른 방법을 선택하겠습니다. 즉, 클래스 자체에서 클래스의 동작을 분리합니다. 그게 무슨 뜻이야? 아시다시피 모든 개체에는 상태(데이터 집합)와 동작(메서드 집합)이 있습니다. 운반 클래스의 동작은 go() , stop()fill() 의 세 가지 메서드로 구성됩니다 . 처음 두 가지 방법은 있는 그대로 괜찮습니다. 그러나 우리는 세 번째 방법을운송 클래스. 이렇게 하면 동작이 클래스에서 분리됩니다(더 정확하게는 동작의 일부만 분리됩니다. 처음 두 메서드는 그대로 유지되기 때문입니다). 그렇다면 fill() 메소드를 어디에 두어야 할까요 ? 아무것도 떠오르지 않습니다 :/ 정확히 있어야 할 곳에 있는 것 같습니다. 별도의 인터페이스인 FillStrategy 로 옮길 것입니다 !

public interface FillStrategy {

   public void fill();
}
왜 그런 인터페이스가 필요한가요? 모두 간단합니다. 이제 이 인터페이스를 구현하는 여러 클래스를 만들 수 있습니다.

public class HybridFillStrategy implements FillStrategy {
  
   @Override
   public void fill() {
       System.out.println("Refuel with gas or electricity — your choice!");
   }
}

public class F1PitstopStrategy implements FillStrategy {
  
   @Override
   public void fill() {
       System.out.println("Refuel with gas only after all other pit stop procedures are complete!");
   }
}

public class StandardFillStrategy implements FillStrategy {
   @Override
   public void fill() {
       System.out.println("Just refuel with gas!");
   }
}
우리는 세 가지 행동 전략을 만들었습니다. 하나는 일반 자동차용, 하나는 하이브리드용, 다른 하나는 포뮬러 1 경주용 자동차용입니다. 각 전략은 다른 급유 알고리즘을 구현합니다. 우리의 경우에는 단순히 콘솔에 문자열을 표시하지만 각 메서드에는 복잡한 논리가 포함될 수 있습니다. 다음에 무엇을 할까요?

public class Conveyance {

   FillStrategy fillStrategy;

   public void fill() {
       fillStrategy.fill();
   }

   public void go() {
       System.out.println("Moving forward");
   }

   public void stop() {
       System.out.println("Braking!");
   }
  
}
Conveyance 상위 클래스 의 필드로 FillStrategy 인터페이스를 사용합니다 . 특정 구현을 나타내는 것이 아니라 인터페이스를 사용하고 있습니다. 자동차 클래스에는 FillStrategy 인터페이스의 특정 구현이 필요합니다.

public class F1Car extends Conveyance {

   public F1Car() {
       this.fillStrategy = new F1PitstopStrategy();
   }
}

public class HybridCar extends Conveyance {

   public HybridCar() {
       this.fillStrategy = new HybridFillStrategy();
   }
}

public class Sedan extends Conveyance {

   public Sedan() {
       this.fillStrategy = new StandardFillStrategy();
   }
}

우리가 얻은 것을 봅시다!

public class Main {

   public static void main(String[] args) {

       Conveyance sedan = new Sedan();
       Conveyance hybrid = new HybridCar();
       Conveyance f1car = new F1Car();

       sedan.fill();
       hybrid.fill();
       f1car.fill();
   }
}
콘솔 출력:

Just refuel with gas! 
Refuel with gas or electricity — your choice! 
Refuel with gas only after all other pit stop procedures are complete!
엄청난! 급유 과정이 정상적으로 작동합니다! 그건 그렇고, 생성자에서 전략을 매개 변수로 사용하는 것을 방해하는 것은 없습니다! 예를 들면 다음과 같습니다.

public class Conveyance {

   private FillStrategy fillStrategy;

   public Conveyance(FillStrategy fillStrategy) {
       this.fillStrategy = fillStrategy;
   }

   public void fill() {
       this.fillStrategy.fill();
   }

   public void go() {
       System.out.println("Moving forward");
   }

   public void stop() {
       System.out.println("Braking!");
   }
}

public class Sedan extends Conveyance {

   public Sedan() {
       super(new StandardFillStrategy());
   }
}



public class HybridCar extends Conveyance {

   public HybridCar() {
       super(new HybridFillStrategy());
   }
}

public class F1Car extends Conveyance {

   public F1Car() {
       super(new F1PitstopStrategy());
   }
}
main() 메서드를 실행해 봅시다 (변경되지 않은 상태로 유지됨). 우리는 같은 결과를 얻습니다! 콘솔 출력:

Just refuel with gas! 
Refuel with gas or electricity — your choice! 
Refuel with gas only after all other pit stop procedures are complete!
전략 설계 패턴은 알고리즘 계열을 정의하고 각 알고리즘을 캡슐화하며 상호 교환이 가능하도록 합니다. 이를 통해 클라이언트가 알고리즘을 사용하는 방식에 관계없이 알고리즘을 수정할 수 있습니다("Head First Design Patterns"라는 책에서 가져온 이 정의는 저에게 훌륭해 보입니다). 디자인 패턴: 전략 - 4우리는 이미 구현이 다른 별도의 인터페이스에서 관심 있는 알고리즘 제품군(자동차에 연료를 보급하는 방법)을 지정했습니다. 우리는 그것들을 차 자체에서 분리했습니다. 이제 특정 급유 알고리즘을 변경해야 하는 경우 자동차 클래스에 어떤 식으로든 영향을 미치지 않습니다. 그리고 호환성을 달성하려면 Conveyance 클래스 에 단일 setter 메서드를 추가하기만 하면 됩니다 .

public class Conveyance {

   FillStrategy fillStrategy;

   public void fill() {
       fillStrategy.fill();
   }

   public void go() {
       System.out.println("Moving forward");
   }

   public void stop() {
       System.out.println("Braking!");
   }

   public void setFillStrategy(FillStrategy fillStrategy) {
       this.fillStrategy = fillStrategy;
   }
}
이제 즉시 전략을 변경할 수 있습니다.

public class Main {

   public static void main(String[] args) {

       Stroller stroller= new Stroller();
       stroller.setFillStrategy(new StandardFillStrategy());

       stroller.fill();
   }
}
유모차가 갑자기 휘발유로 움직이기 시작하면 우리 프로그램이 이 시나리오를 처리할 준비가 될 것입니다 :) 그게 다입니다! 실제 프로젝트를 작업할 때 틀림없이 필수적이고 도움이 될 디자인 패턴을 하나 더 배웠습니다 :) 다음 시간까지!
코멘트
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION