Cześć,
od jakiegoś czasu interesuję się wzorcami projektowymi związanymi z #programowanie. W celu lepszego przyswojenia informacji lubię sobie zapisywać rzeczy, które właśnie poznałem. Dzięki temu zapamiętanie będzie polegało na przetworzeniu czegoś na przykładzie i wytłumaczenie tego na swój sposób (Tak jak to właśnie robię). Generalnie każdy dobry programista spotkał lub spotka się z pewnym schematem budowy kodu. Z czasem zacznie zastanawiać się czy ta powtarzająca konstrukcja nie ma jakieś nazwy.
Może on spotkać się właśnie na przykład na wzorzec strategii.
Kiedy wykorzystujemy taki wzorzec?
Przykładem mogą być pewne zniżki. Chcemy, aby pewien produkt posiadał różne ceny ze względu na np. kartę studencką, zniżka dla starców itd.
Dzięki wzorcowi strategii nie musimy tworzyć wielu metod, które musiałyby obliczać if student to zniżka 20% else if starzec zniżka 50%
Aby zapobiec rozgałęzianiu ifami możemy użyć klas.
Załóżmy, że mamy sklep i chcemy dawać zniżkę 20%.
Zacznijmy od zdefiniowania klasy Product:
public class Product {
private String name;
private BigDecimal price;
public Product(String name, BigDecimal price) {
this.name = name;
this.price = price;
}
}
Zatrzymajmy się na chwilę i zastanówmy się jak wygląda "najprostsza wersja". Jak zrobiłby to początkujący programista. Pewnie pomyślałby, że klasa Product może sobie w metodzie computeDiscount obliczyć każdą zniżkę, najwyżej w parametrze podam jej nazwę i gitara xd
Wyglądałoby to tak:
public class Product {
private String name;
private BigDecimal price;
public Product(String name, BigDecimal price) {
this.name = name;
this.price = price;
}
public String computeDiscount(String discountName) {
if(discountName .equals("student")) {
return String.valueOf(price + (price * 20)/100);
}
if(discountName .equals("newclient")) {
return price + String.valueOf((price * 10)/100);
}
}
}
Co jeżeli chcielibyśmy zaimplementować dla 10 różnych typów zniżek? DODAJMY WIĘCEJ IFÓW!!!!!
No w sumie pewnie to by zadziałało, ale powodzenia z utrzymaniem tego kodu w sensownym porządku.
Dlatego fajnie jest użyć wzorca strategii, żeby powiedzieć, że to klasa będzie miała swój algorytm a ten algorytm będzie używany w ustalonych sytuacjach.
No dobra, to jak ten wzorzec użyć?
Żeby to zrobić, stwórzmy sobie interfejs i nazwijmy Discount:
public interface Discount {
BigDecimal compute(BigDecimal price);
}
Ten tajemniczy BigDecimal, to zmienna pozwalająca na dokładniejsze operowanie na "pieniądzach"
Oczywiście zakładam, że każdy umie zaimportować xd
No dobra, to może teraz zaimplementujmy ten interfejs i zróbmy algorytm obliczania zniżki dla ucznia:
public class StudentDiscount implements Discount {
public BigDecimal compute(BigDecimal price) {
BigDecimal discount = new BigDecimal("0.8);
return price.multiply(discount)
}
}
Powyższy kod mówi, weź cenę, oblicz 80% z tej ceny i zwróć wynik.
Weźmy analogicznie zróbmy zniżkę dla nowego klienta:
public class NewClientDiscount implements Discount {
public BigDecimal compute(BigDecimal price) {
BigDecimal discount = new BigDecimal("0.9);
return price.multiply(discount)
}
}
Dobra, mając te dwa algorytmy obliczania zniżek, użyjmy ich interfejs do klasy Product:
public class Product {
private String name;
private BigDecimal price;
private Discount discountStrategy;
public Product(String name, BigDecimal price) {
this.name = name;
this.price = price;
}
public BigDecimal computePrice() {
return discountStrategy.compute(price);
}
public void applyDiscount(Discount discount) {
this.discountStrategy = discount;
}
}
No dobra, a co w przypadku, kiedy chcemy mieć pełną cenę?
Robimy klasę "nic nie robiącą":
public class NoDiscount implements Discount {
public BigDecimal compute(BigDecimal price) {
return price;
}
}
Ok, to jak wygląda końcowa wersja naszej klasy Product?
public class Product {
private String name;
private BigDecimal price;
private Discount discountStrategy;
public Product(String name, BigDecimal price) {
this.name = name;
this.price = price;
this.discountStrategy = new NoDiscount();
}
public BigDecimal computePrice() {
return discountStrategy.compute(price);
}
public void applyDiscount(Discount discount) {
this.discountStrategy = discount;
}
}
Teraz możemy zastosować to wszystko w praktyce.
Załóżmy, że mamy stworzyć aplikację, w której klient zamawia pewne produkty i sprzedawca dostaje sumę jaką klient musi zapłacić. Oczywiście sklep jest przyjazny klientowi i kasjerka wprowadzi zniżki podczas zakupów.
Musimy dodać nową klasę do zamówień i nazwijmy ją Order:
public class Order {
private ArrayList<Product> products = new ArrayList<Product>();
private String summarise() {
BigDecimal summarisedPrice = new BigDecimal("0");
for(Product : products) {
summarisedPrice = BigDecimal.add(product.computePrice());
}
}
public void addProduct(Product product) {
products.add(product);
}
public void removeProduct(Product product) {
if(products.contains(product) {
product.remove(product);
}
}
}
Powyższa klasa pozwala dodać produkt do zamówienia, bądź go usunąć oraz klasa pozwala zsumować całe zamówienie.
No dobrze, to teraz użyjmy wszystkie klasy w głównej klasie w metodzie statyczne j main:
public class Main {
public static main(String[] args) {
Product p1 = new Product("Drink Mix", 5);
Product p2 = new Product("Snacks Nice", 5);
Product p3 = new Product("Water COOL", 5);
p1.applyDiscount(new NewClientDiscount());
p2.applyDiscount(new StudentDiscount());
Order o = new Order();
o.addProduct(p1);
o.addProduct(p2);
o.addProduct(p3);
System.out.println("Łączny koszt zakupów, to " + o.summarisedPrice());
}
}
W powyższym kodzie stworzyliśmy 3 produkty, do produktu p1 i p2 dołączyliśmy zniżki a p3 jest bez zniżki.
Do zamówienia dołączyliśmy 3 produkty i obliczyliśmy sumę pieniędzy zapłaconą za produkty.
I tak oto można zaimplementować wzorzec strategię.
Jakie są zalety tego wzorca?
- nie musisz ruszać metody compute liczącej zniżki
- kiedy chcesz zaimplementować nową zniżkę
- robisz nową implementację interfejsu Discount
- nie musisz robić if train xd
A jakie są te złe strony tego wzorca?
- musisz w sumie wiedzieć jakiego algorytmu chcesz użyć
- więcej klas musisz stworzyć, żeby obsłużyć wszystkie wymagane algorytmy (niektórzy nie lubią dużo klas) ((Najlepiej wszystko w jednej klasie ^^))
No dobra, to chyba tyle na razie.
Powodzenia w boju!
PS. Post został napisany na podstawie mojego starego postu na tej stronie: https://grzegorz2047.github.io/