
Стратегия на Java
Стратегия — это поведенческий паттерн, выносит набор алгоритмов в собственные классы и делает их взаимозаменимыми.
Другие объекты содержат ссылку на объект-стратегию и делегируют ей работу. Программа может подменить этот объект другим, если требуется иной способ решения задачи.
Сложность:
Популярность:
Применимость: Стратегия часто используется в Java-коде, особенно там, где нужно подменять алгоритм во время выполнения программы. Начиная с Java 8, многие примеры стратегии можно заменить простыми lambda-выражениями.
Примеры Стратегии в стандартных библиотеках Java:
-
java.util.Comparator#compare()
, вызываемые изCollections#sort()
. -
javax.servlet.http.HttpServlet
: методservice()
, а также все методыdoXXX()
принимают объектыHttpServletRequest
иHttpServletResponse
в параметрах.
Признаки применения паттерна: Класс делегирует выполнение вложенному объекту абстрактного типа или интерфейса.
Методы оплаты в интернет магазине
В этом примере Стратегия реализует выбор платёжного метода в интернет магазине. Когда пользователь сформировал заказ, он получает выбор из нескольких платёжных стредств: электронного кошелька или кредитной карты.
В данном случае конкретные стратегии платёжных методов не только проводят саму оплату, но и собирают необходимые данные на форме заказа.
strategies
strategies/PayStrategy.java: Общий интерфейс стратегий оплаты
package refactoring_guru.strategy.example.strategies; /** * Общий интерфейс всех стратегий. */ public interface PayStrategy { boolean pay(int paymentAmount); void collectPaymentDetails(); }
strategies/PayByPayPal.java: Оплата через PayPal
package refactoring_guru.strategy.example.strategies; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.util.HashMap; import java.util.Map; /** * Конкретная стратегия. Реализует оплату корзины интернет магазина через * платежную систему PayPal. */ public class PayByPayPal implements PayStrategy { private static final Map<String, String> DATA_BASE = new HashMap<>(); private final BufferedReader READER = new BufferedReader(new InputStreamReader(System.in)); private String email; private String password; private boolean signedIn; static { DATA_BASE.put("amanda1985", "amanda@ya.com"); DATA_BASE.put("qwerty", "john@amazon.eu"); } /** * Собираем данные от клиента. */ @Override public void collectPaymentDetails() { try { while (!signedIn) { System.out.print("Enter the user's email: "); email = READER.readLine(); System.out.print("Enter the password: "); password = READER.readLine(); if (verify()) { System.out.println("Data verification has been successful."); } else { System.out.println("Wrong email or password!"); } } } catch (IOException ex) { ex.printStackTrace(); } } private boolean verify() { setSignedIn(email.equals(DATA_BASE.get(password))); return signedIn; } /** * Если клиент уже вошел в систему, то для следующей оплаты данные вводить * не придется. */ @Override public boolean pay(int paymentAmount) { if (signedIn) { System.out.println("Paying " + paymentAmount + " using PayPal."); return true; } else { return false; } } private void setSignedIn(boolean signedIn) { this.signedIn = signedIn; } }
strategies/PayByCreditCard.java: Оплата кредиткой
package refactoring_guru.strategy.example.strategies; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; /** * Конкретная стратегия. Реализует оплату корзины интернет магазина кредитной * картой клиента. */ public class PayByCreditCard implements PayStrategy { private final BufferedReader READER = new BufferedReader(new InputStreamReader(System.in)); private CreditCard card; /** * Собираем данные карты клиента. */ @Override public void collectPaymentDetails() { try { System.out.print("Enter the card number: "); String number = READER.readLine(); System.out.print("Enter the card expiration date 'mm/yy': "); String date = READER.readLine(); System.out.print("Enter the CVV code: "); String cvv = READER.readLine(); card = new CreditCard(number, date, cvv); // Валидируем номер карты... } catch (IOException ex) { ex.printStackTrace(); } } /** * После проверки карты мы можем совершить оплату. Если клиент продолжает * покупки, мы не запрашиваем карту заново. */ @Override public boolean pay(int paymentAmount) { if (cardIsPresent()) { System.out.println("Paying " + paymentAmount + " using Credit Card."); card.setAmount(card.getAmount() - paymentAmount); return true; } else { return false; } } private boolean cardIsPresent() { return card != null; } }
strategies/CreditCard.java: Кредитная карта
package refactoring_guru.strategy.example.strategies; /** * Очень наивная реализация кредитной карты. */ public class CreditCard { private int amount; private String number; private String date; private String cvv; CreditCard(String number, String date, String cvv) { this.amount = 100_000; this.number = number; this.date = date; this.cvv = cvv; } public void setAmount(int amount) { this.amount = amount; } public int getAmount() { return amount; } }
order
order/Order.java: Класс заказа
package refactoring_guru.strategy.example.order; import refactoring_guru.strategy.example.strategies.PayStrategy; /** * Класс заказа. Ничего не знает о том каким способом (стратегией) будет * расчитыватся клиент, а просто вызывает метод оплаты. Все остальное стратегия * делает сама. */ public class Order { private int totalCost = 0; private boolean isClosed = false; public void processOrder(PayStrategy strategy) { strategy.collectPaymentDetails(); // Здесь мы могли бы забрать и сохранить платежные данные из стратегии. } public void setTotalCost(int cost) { this.totalCost += cost; } public int getTotalCost() { return totalCost; } public boolean isClosed() { return isClosed; } public void setClosed() { isClosed = true; } }
Demo.java: Клиентский код
package refactoring_guru.strategy.example; import refactoring_guru.strategy.example.order.Order; import refactoring_guru.strategy.example.strategies.PayByCreditCard; import refactoring_guru.strategy.example.strategies.PayByPayPal; import refactoring_guru.strategy.example.strategies.PayStrategy; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.util.HashMap; import java.util.Map; /** * Первый в мире консольный интерет магазин. */ public class Demo { private static Map<Integer, Integer> priceOnProducts = new HashMap<>(); private static BufferedReader reader = new BufferedReader(new InputStreamReader(System.in)); private static Order order = new Order(); private static PayStrategy strategy; static { priceOnProducts.put(1, 2200); priceOnProducts.put(2, 1850); priceOnProducts.put(3, 1100); priceOnProducts.put(4, 890); } public static void main(String[] args) throws IOException { while (!order.isClosed()) { int cost; String continueChoice; do { System.out.print("Please, select a product:" + "\n" + "1 - Mother board" + "\n" + "2 - CPU" + "\n" + "3 - HDD" + "\n" + "4 - Memory" + "\n"); int choice = Integer.parseInt(reader.readLine()); cost = priceOnProducts.get(choice); System.out.print("Count: "); int count = Integer.parseInt(reader.readLine()); order.setTotalCost(cost * count); System.out.print("Do you wish to continue selecting products? Y/N: "); continueChoice = reader.readLine(); } while (continueChoice.equalsIgnoreCase("Y")); if (strategy == null) { System.out.println("Please, select a payment method:" + "\n" + "1 - PalPay" + "\n" + "2 - Credit Card"); String paymentMethod = reader.readLine(); // Клиент создаёт различные стратегии на основании // пользовательских данных, конфигурации и прочих параметров. if (paymentMethod.equals("1")) { strategy = new PayByPayPal(); } else { strategy = new PayByCreditCard(); } } // Объект заказа делегирует сбор платёжных данны стратегии, т.к. // только стратегии знают какие данные им нужны для приёма оплаты. order.processOrder(strategy); System.out.print("Pay " + order.getTotalCost() + " units or Continue shopping? P/C: "); String proceed = reader.readLine(); if (proceed.equalsIgnoreCase("P")) { // И наконец, стратегия запускает приём платежа. if (strategy.pay(order.getTotalCost())) { System.out.println("Payment has been successful."); } else { System.out.println("FAIL! Please, check your data."); } order.setClosed(); } } } }
OutputDemo.txt: Результат выполнения
Please, select a product: 1 - Mother board 2 - CPU 3 - HDD 4 - Memory 1 Count: 2 Do you wish to continue selecting products? Y/N: y Please, select a product: 1 - Mother board 2 - CPU 3 - HDD 4 - Memory 2 Count: 1 Do you wish to continue selecting products? Y/N: n Please, select a payment method: 1 - PalPay 2 - Credit Card 1 Enter the user's email: user@example.com Enter the password: qwerty Wrong email or password! Enter user email: amanda@ya.com Enter password: amanda1985 Data verification has been successful. Pay 6250 units or Continue shopping? P/C: p Paying 6250 using PayPal. Payment has been successful.