JavaRush /Java Blog /Random-KO /디자인 패턴 “전략”

디자인 패턴 “전략”

Random-KO 그룹에 게시되었습니다
안녕하세요! 이전 강의에서 우리는 이미 “디자인 패턴”이라는 개념을 접했습니다. 잊어버린 경우를 대비해 상기시켜 드리겠습니다. 이 용어는 프로그래밍의 일반적인 문제에 대한 특정 표준 솔루션을 나타냅니다. 디자인 패턴 “전략” - 1JavaRush에서는 거의 모든 질문에 대한 답변이 Google에서 검색될 수 있다고 자주 말합니다. 따라서 누군가가 이미 귀하와 유사한 문제를 성공적으로 해결했을 것입니다. 따라서 패턴은 문제 상황을 해결하기 위한 가장 일반적인 문제 또는 방법에 대한 시간 테스트와 실습 테스트를 거친 솔루션입니다. 이것은 어떤 경우에도 스스로 발명할 필요가 없는 바로 "자전거"이지만 적용 방법과 시기를 알아야 합니다. :) 패턴의 또 다른 작업은 아키텍처를 단일 표준으로 가져오는 것입니다. 다른 사람의 코드를 읽는 것은 쉬운 일이 아닙니다! 동일한 문제가 여러 가지 방법으로 해결될 수 있기 때문에 모든 사람이 다르게 작성합니다. 그러나 패턴을 사용하면 다양한 프로그래머가 코드의 모든 줄을 파고들지 않고도 프로그램의 논리를 이해할 수 있습니다(처음으로 보더라도!). 오늘 우리는 "전략"이라는 가장 일반적인 패턴 중 하나를 살펴보겠습니다. 디자인 패턴 “전략” - 2Car 개체와 적극적으로 작동하는 프로그램을 작성한다고 가정해 보겠습니다. 이 경우 우리 프로그램이 정확히 무엇을 하는지는 특별히 중요하지 않습니다. 이를 위해 우리는 하나의 상위 클래스 Auto와 세 개의 하위 클래스( Sedan, Truck및 ) 가 있는 상속 시스템을 만들었습니다 F1Car.
public class Auto {

   public void gas() {
       System.out.println("Едем вперед");
   }

   public void stop() {

       System.out.println("Тормозим!");
   }
}

public class Sedan extends Auto {
}

public class Truck extends Auto {
}

public class F1Car extends Auto {
}
세 하위 클래스 모두 상위 클래스로부터 두 가지 표준 방법을 상속받습니다. gas()우리 stop() 프로그램은 매우 간단합니다. 자동차는 앞으로만 주행하고 브레이크를 밟을 수 있습니다. 작업을 계속하면서 우리는 자동차에 새로운 방법 fill()(급유)을 추가하기로 결정했습니다. 상위 클래스에 추가해 보겠습니다 Auto.
public class Auto {

   public void gas() {
       System.out.println("Едем вперед");
   }

   public void stop() {

       System.out.println("Тормозим!");
   }

   public void fill() {
       System.out.println("Заправить бензин!");
   }
}
이렇게 간단한 상황에서도 문제가 발생할 수 있을 것 같나요? 사실, 그것들은 이미 일어났습니다... 디자인 패턴 “전략” - 3
public class ChildrenBuggies extends Auto {

   public void fill() {

       //хм... Это детский багги, его не надо заправлять :/
   }
}
우리 프로그램에는 일반적인 개념에 맞지 않는 자동차, 즉 어린 이용 버기가 나타났습니다. 페달로 구동되거나 무선으로 제어될 수 있지만 한 가지 확실한 점은 휘발유를 넣을 곳이 없다는 것입니다. 우리의 상속 체계로 인해 공통 메서드가 필요하지 않은 클래스에도 공통 메서드를 제공하게 되었습니다. 그러한 상황에서 우리는 어떻게 해야 합니까? 예를 들어 fill()클래스의 메서드를 재정 ChildrenBuggies의하여 버기카에 연료를 공급하려고 할 때 아무 일도 일어나지 않도록 할 수 있습니다.
public class ChildrenBuggies extends Auto {

   @Override
   public void fill() {
       System.out.println("Игрушечную машину нельзя заправить!");
   }
}
그러나 이 솔루션은 적어도 코드 중복으로 인해 성공했다고 보기 어렵습니다. 예를 들어 대부분의 클래스는 상위 클래스의 메서드를 사용하지만 다른 클래스는 이를 재정의해야 합니다. 15개의 클래스가 있고 5-6개에서 동작을 재정의해야 한다면 코드 중복이 상당히 광범위해질 것입니다. 인터페이스가 우리에게 도움이 될 수 있을까요? 예를 들면 다음과 같습니다.
public interface Fillable {

   public void fill();
}
Fillable우리는 하나의 메소드로 인터페이스를 생성할 것입니다 fill(). 따라서 연료를 보급해야 하는 자동차는 이 인터페이스를 구현하지만 다른 자동차(예: 버기카)는 구현하지 않습니다. 하지만 이 옵션도 우리에게는 적합하지 않습니다. 우리의 클래스 계층 구조는 미래에 매우 큰 숫자로 성장할 수 있습니다(세계에 얼마나 많은 종류의 자동차가 있는지 상상해 보십시오). . fill()_ 여기에서는 모든 클래스에서 이를 구현해야 합니다! 만약 50개가 있다면 어떨까요? 그리고 프로그램이 자주 변경된다면(그리고 실제 프로그램에서는 거의 항상 이런 일이 일어날 것입니다!), 우리는 50개 클래스 모두 사이를 맴돌며 각 클래스의 동작을 수동으로 변경해야 합니다. 그러면 우리는 결국 무엇을 해야 할까요? 문제를 해결하려면 다른 길을 선택해 봅시다. 즉, 우리 클래스의 동작을 클래스 자체와 분리해 보겠습니다. 무슨 뜻이에요? 아시다시피 모든 객체에는 상태(데이터 집합)와 동작(메서드 집합)이 있습니다. 우리 머신 클래스의 동작은 , gas()stop()의 세 가지 메소드로 구성됩니다 fill(). 처음 두 가지 방법은 괜찮습니다. 하지만 우리는 세 번째 메소드를 클래스 밖으로 옮길 것입니다 Auto. 이는 클래스에서 동작을 분리하는 것입니다(더 정확하게는 동작의 일부만 분리합니다. 처음 두 메서드는 그대로 유지됩니다). 메소드를 어디로 옮겨야 할까요 fill()? 당장 떠오르는 것은 아무것도 없습니다 :/ 그는 완전히 제 자리에 있는 것 같았습니다. 별도의 인터페이스로 옮겨드리겠습니다- FillStrategy!
public interface FillStrategy {

   public void fill();
}
이 인터페이스가 왜 필요한가요? 간단 해. 이제 이 인터페이스를 구현할 여러 클래스를 만들 수 있습니다.
public class HybridFillStrategy implements FillStrategy {

   @Override
   public void fill() {
       System.out.println("Заправляем бензином or электричеством на выбор!");
   }
}

public class F1PitstopStrategy implements FillStrategy {

   @Override
   public void fill() {
       System.out.println("Заправляем бензин только после всех остальных procedures пит-стопа!");
   }
}

public class StandartFillStrategy implements FillStrategy {
   @Override
   public void fill() {
       System.out.println("Просто заправляем бензин!");
   }
}
우리는 기존 자동차, 하이브리드, 포뮬러 1 자동차의 세 가지 행동 전략을 만들었습니다. 각 전략은 별도의 급유 알고리즘을 구현합니다. 우리의 경우 이는 단지 콘솔로 출력되지만 메서드 내부에는 복잡한 로직이 있을 수 있습니다. 다음에는 이걸 어떻게 해야 할까요?
public class Auto {

   FillStrategy fillStrategy;

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

   public void gas() {
       System.out.println("Едем вперед");
   }

   public void stop() {
       System.out.println("Тормозим!");
   }

}
우리는 인터페이스를 FillStrategy상위 클래스의 필드로 사용합니다 Auto. 참고: 특정 구현을 지정하지 않고 인터페이스를 사용합니다. FillStrategy그리고 하위 자동차 클래스에 인터페이스의 구체적인 구현이 필요합니다 .
public class F1Car extends Auto {

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

public class HybridAuto extends Auto {

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

public class Sedan extends Auto {

   public Sedan() {
       this.fillStrategy = new StandartFillStrategy();
   }
}
우리가 무엇을 얻었는지 봅시다:
public class Main {

   public static void main(String[] args) {

       Auto sedan = new Sedan();
       Auto hybrid = new HybridAuto();
       Auto f1car = new F1Car();

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

Просто заправляем бензин!
Заправляем бензином or электричеством на выбор!
Заправляем бензин только после всех остальных procedures пит-стопа!
좋습니다. 재급유 과정이 정상적으로 작동합니다! 그건 그렇고, 생성자에서 전략을 매개변수로 사용하는 것을 방해하는 것은 없습니다! 예를 들어 다음과 같습니다.
public class Auto {

   private FillStrategy fillStrategy;

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

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

   public void gas() {
       System.out.println("Едем вперед");
   }

   public void stop() {
       System.out.println("Тормозим!");
   }
}

public class Sedan extends Auto {

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



public class HybridAuto extends Auto {

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

public class F1Car extends Auto {

   public F1Car() {
       super(new F1PitstopStrategy());
   }
}
우리의 방법을 실행하고 main()(변경되지 않은 상태로 유지) 동일한 결과를 얻습니다! 콘솔 출력:

Просто заправляем бензин!
Заправляем бензином or электричеством на выбор!
Заправляем бензин только после всех остальных procedures пит-стопа!
전략 패턴은 알고리즘 계열을 정의하고 각 알고리즘을 캡슐화하며 상호 교환이 가능하도록 보장합니다. 이를 통해 클라이언트 측에서의 사용 여부에 관계없이 알고리즘을 수정할 수 있습니다(이 정의는 "Exploring Design Patterns"라는 책에서 가져온 것이며 제가 보기에는 매우 성공한 것 같습니다). 디자인 패턴 “전략” - 4우리는 관심 있는 알고리즘 제품군(급유 차량 유형)을 여러 구현을 통해 별도의 인터페이스로 분리했습니다. 우리는 그것들을 자동차의 본질과 분리했습니다. 따라서 이제 이 또는 저 급유 프로세스를 변경해야 하는 경우 이는 어떤 식으로든 자동차 클래스에 영향을 미치지 않습니다. 호환성에 관해서는 이를 달성하려면 클래스에 하나의 setter 메소드를 추가하면 됩니다 Auto.
public class Auto {

   FillStrategy fillStrategy;

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

   public void gas() {
       System.out.println("Едем вперед");
   }

   public void stop() {
       System.out.println("Тормозим!");
   }

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

   public static void main(String[] args) {

       ChildrenBuggies buggies = new ChildrenBuggies();
       buggies.setFillStrategy(new StandartFillStrategy());

       buggies.fill();
   }
}
갑자기 어린 이용 버기카에 휘발유가 가득 차기 시작하면 우리 프로그램은 그러한 시나리오에 대비할 것입니다 :) 실제로 그게 전부입니다! 여러분은 의심할 여지없이 필요하고 실제 프로젝트 작업 시 여러 번 도움이 될 또 다른 디자인 패턴을 배웠습니다 :) 또 만나요!
코멘트
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION