JavaRush /Blog Java /Random-PL /Wzorzec projektowy „Strategia”

Wzorzec projektowy „Strategia”

Opublikowano w grupie Random-PL
Cześć! W poprzednich wykładach zetknęliśmy się już z pojęciem „wzorca projektowego”. Jeśli zapomniałeś, przypomnijmy: termin ten oznacza pewne standardowe rozwiązanie typowego problemu w programowaniu. Wzorzec projektowy „Strategia” - 1W JavaRush często mówimy, że odpowiedź na prawie każde pytanie można znaleźć w Google. Dlatego prawdopodobnie ktoś już pomyślnie rozwiązał problem podobny do Twojego. Zatem wzorce to sprawdzone w czasie i praktyce rozwiązania najczęstszych problemów lub metody rozwiązywania sytuacji problemowych. To właśnie takie „rowery”, których w żadnym wypadku nie trzeba wymyślać samemu, ale trzeba wiedzieć, jak i kiedy je zastosować :) Kolejnym zadaniem wzorców jest doprowadzenie architektury do jednolitego standardu. Czytanie cudzego kodu nie jest łatwym zadaniem! Każdy pisze to inaczej, bo ten sam problem można rozwiązać na wiele sposobów. Ale użycie wzorców pozwala różnym programistom zrozumieć logikę programu bez zagłębiania się w każdą linijkę kodu (nawet jeśli widzą to po raz pierwszy!). Dzisiaj przyjrzymy się jednemu z najpopularniejszych wzorców zwanym „Strategią”. Wzorzec projektowy „Strategia” - 2Wyobraźmy sobie, że piszemy program, który aktywnie współpracuje z obiektem Car. W tym przypadku nie jest nawet szczególnie ważne, co dokładnie robi nasz program. Aby to zrobić, stworzyliśmy system dziedziczenia z jedną klasą nadrzędną Autoi trzema klasami podrzędnymi: Sedan, Trucki 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 {
}
Wszystkie trzy klasy podrzędne dziedziczą po rodzicu dwie standardowe metody - gas()a stop() nasz program jest całkowicie prosty: samochody mogą jechać tylko do przodu i hamować. Kontynuując naszą pracę, postanowiliśmy dodać do samochodów nową metodę - fill()(tankowanie). Dodajmy to do klasy nadrzędnej Auto:
public class Auto {

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

   public void stop() {

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

   public void fill() {
       System.out.println("Заправить бензин!");
   }
}
Wydawałoby się, że w tak prostej sytuacji mogą pojawić się problemy? A właściwie już powstały... Wzorzec projektowy „Strategia” - 3
public class ChildrenBuggies extends Auto {

   public void fill() {

       //хм... Это детский багги, его не надо заправлять :/
   }
}
W naszym programie pojawił się samochód nie mieszczący się w ogólnej koncepcji - wózek dziecięcy. Może być napędzany pedałami lub sterowany radiowo, ale jedno jest pewne – nie ma gdzie wlać do niego benzyny. Nasz schemat dziedziczenia spowodował, że udostępniliśmy wspólne metody nawet klasom, które ich nie potrzebują. Co powinniśmy zrobić w takiej sytuacji? No cóż, możesz na przykład przesłonić metodę fill()w klasie ChildrenBuggies, aby przy próbie zatankowania buggy nic się nie działo:
public class ChildrenBuggies extends Auto {

   @Override
   public void fill() {
       System.out.println("Игрушечную машину нельзя заправить!");
   }
}
Ale to rozwiązanie trudno nazwać sukcesem, przynajmniej ze względu na powielanie kodu. Na przykład większość klas użyje metody z klasy nadrzędnej, ale inne klasy będą zmuszone ją zastąpić. Jeśli mamy 15 klas, a w 5-6 będziemy zmuszeni zastąpić zachowanie, powielanie kodu stanie się dość rozległe. Może interfejsy nam pomogą? Na przykład ten:
public interface Fillable {

   public void fill();
}
Stworzymy interfejs Fillablez jedną metodą fill(). W związku z tym te samochody, które wymagają zatankowania, wdrożą ten interfejs, ale inne samochody (na przykład nasz buggy) nie. Ale ta opcja też nam nie odpowiada. Nasza hierarchia klas może w przyszłości urosnąć do bardzo dużej liczby (wyobraźcie sobie, ile różnych typów samochodów jest na świecie). Porzuciliśmy poprzednią opcję dziedziczenia, ponieważ nie chcieliśmy zastępować opcji fill(). Tutaj będziemy musieli wdrożyć to na wszystkich zajęciach! A co jeśli będzie ich 50? A jeśli w naszym programie będą wprowadzane częste zmiany (a w prawdziwych programach będzie to prawie zawsze się zdarzać!), będziemy musieli biegać z wywieszonym językiem pomiędzy wszystkimi 50 klasami i ręcznie zmieniać zachowanie każdej z nich. Co więc powinniśmy w końcu zrobić? Aby rozwiązać nasz problem, wybierzmy inną ścieżkę. Mianowicie oddzielmy zachowanie naszej klasy od samej klasy. Co to znaczy? Jak wiadomo, każdy obiekt ma stan (zestaw danych) i zachowanie (zestaw metod). Zachowanie naszej klasy maszyn składa się z trzech metod gas()- stop()i fill(). Pierwsze dwie metody są w porządku. Ale trzecią metodę przeniesiemy poza klasę Auto. Będzie to polegało na oddzieleniu zachowania od klasy (dokładniej oddzielamy tylko część zachowania – dwie pierwsze metody pozostają bez zmian). Gdzie powinniśmy przenieść naszą metodę fill()? Nic od razu nie przychodzi mi do głowy :/ Wydawało się, że jest już zupełnie na swoim miejscu. Przeniesiemy go do osobnego interfejsu - FillStrategy!
public interface FillStrategy {

   public void fill();
}
Dlaczego potrzebujemy tego interfejsu? To proste. Teraz możemy stworzyć kilka klas, które będą implementować ten interfejs:
public class HybridFillStrategy implements FillStrategy {

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

public class F1PitstopStrategy implements FillStrategy {

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

public class StandartFillStrategy implements FillStrategy {
   @Override
   public void fill() {
       System.out.println("Просто заправляем бензин!");
   }
}
Stworzyliśmy trzy strategie zachowań – dla samochodów konwencjonalnych, dla hybryd i dla samochodów Formuły 1. Każda strategia implementuje odrębny algorytm tankowania. W naszym przypadku jest to po prostu sygnał wyjściowy na konsolę, ale w metodzie może znajdować się skomplikowana logika. Co powinniśmy z tym dalej zrobić?
public class Auto {

   FillStrategy fillStrategy;

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

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

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

}
Używamy naszego interfejsu FillStrategyjako pola w klasie nadrzędnej Auto. Uwaga: nie określamy konkretnej implementacji, ale raczej korzystamy z interfejsu. Będziemy potrzebować konkretnych implementacji interfejsu FillStrategyw klasach samochodów dziecięcych:
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();
   }
}
Zobaczmy, co mamy:
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();
   }
}
Wyjście konsoli:

Просто заправляем бензин!
Заправляем бензином Lub электричеством на выбор!
Заправляем бензин только после всех остальных procedury пит-стопа!
Świetnie, proces tankowania przebiega tak, jak powinien! Swoją drogą nic nie stoi na przeszkodzie, abyśmy wykorzystali strategię jako parametr w konstruktorze! Na przykład tak:
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());
   }
}
Uruchommy naszą metodę main()(pozostanie niezmieniona) i otrzymamy ten sam wynik! Wyjście konsoli:

Просто заправляем бензин!
Заправляем бензином Lub электричеством на выбор!
Заправляем бензин только после всех остальных procedury пит-стопа!
Wzorzec Strategii definiuje rodzinę algorytmów, hermetyzuje każdy z nich i zapewnia ich wymienność. Pozwala na modyfikację algorytmów niezależnie od ich użycia po stronie klienta (definicja ta zaczerpnięta jest z książki „Exploring Design Patterns” i wydaje mi się niezwykle skuteczna). Wzorzec projektowy „Strategia” - 4Wyodrębniliśmy interesującą nas rodzinę algorytmów (rodzaje tankujących samochodów) na osobne interfejsy z kilkoma implementacjami. Oddzieliliśmy je od samej istoty samochodu. Dlatego teraz, jeśli będziemy musieli wprowadzić jakiekolwiek zmiany w tym czy innym procesie tankowania, nie będzie to miało żadnego wpływu na nasze klasy samochodów. Jeśli chodzi o wymienność, aby to osiągnąć, wystarczy dodać do naszej klasy jedną metodę ustawiającą 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;
   }
}
Teraz możemy zmieniać strategie w locie:
public class Main {

   public static void main(String[] args) {

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

       buggies.fill();
   }
}
Jeśli nagle samochody do wózków dziecięcych zaczną być napełniane benzyną, nasz program będzie przygotowany na taki scenariusz :) Właściwie to wszystko! Poznałeś kolejny wzorzec projektowy, który z pewnością będzie Ci potrzebny i który nie raz przyda Ci się w pracy nad prawdziwymi projektami :) Do zobaczenia!
Komentarze
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION