策略模式是一種行為設計模式, 它能讓你定義一系列算法, 並將每種算法分別放入獨立的類中, 以使算法的對象能夠相互替換。
Java 8 開始支持 lambda 方法, 它可作為一種替代策略模式的簡單方式。
這里有一些核心 Java 程序庫中策略模式的示例:
- 對 java.util.Comparator#compare() 的調用來自
Collections#sort()
. - javax.servlet.http.HttpServlet:
service()
方法, 還有所有接受HttpServletRequest
和HttpServletResponse
對象作為參數的doXXX()
方法。 - javax.servlet.Filter#doFilter()
識別方法: 策略模式可以通過允許嵌套對象完成實際工作的方法以及允許將該對象替換為不同對象的設置器來識別。
策略模式結構
樣例
策略模式被用於在電子商務應用中實現各種支付方法。 客戶選中希望購買的商品后需要選擇一種支付方式: Paypal 或者信用卡。
具體策略不僅會完成實際的支付工作, 還會改變支付表單的行為, 並在表單中提供相應的字段來記錄支付信息。
通用的支付方法接口
package behavioral.strategy;
public interface PayStrategy {
boolean pay(int paymentAmount);
void collectPaymentDetails();
}
使用 PayPal 支付
package behavioral.strategy;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.HashMap;
import java.util.Map;
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("hua", "hua@qq.com");
DATA_BASE.put("user", "user@qq.com");
}
@Override
public boolean pay(int paymentAmount) {
if (signedIn) {
System.out.println("Paying " + paymentAmount + " using PayPal.");
return true;
} else {
return false;
}
}
@Override
public void collectPaymentDetails() {
try {
while (!signedIn) {
System.out.println("Enter the user's email:");
email = READER.readLine();
System.out.println("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 e) {
e.printStackTrace();
}
}
private boolean verify() {
setSignedIn(email.equals(DATA_BASE.get(password)));
return signedIn;
}
private void setSignedIn(boolean signedIn) {
this.signedIn = signedIn;
}
}
使用信用卡支付
package behavioral.strategy;
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 boolean pay(int paymentAmount) {
if (cardIsPresent()) {
System.out.println("Paying " + paymentAmount + " using Credit Card.");
card.setAmount(card.getAmount() - paymentAmount);
return true;
} else {
return false;
}
}
@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);
//credit card 有效性檢查
} catch (IOException e) {
e.printStackTrace();
}
}
private boolean cardIsPresent() {
return card != null;
}
}
信用卡類
package behavioral.strategy;
public class CreditCard {
private int amount;
private String number;
private String date;
private String cvv;
public CreditCard(String number, String date, String cvv) {
this.amount = 100_000;
this.number = number;
this.date = date;
this.cvv = cvv;
}
public int getAmount() {
return amount;
}
public void setAmount(int amount) {
this.amount = amount;
}
}
訂單類
package behavioral.strategy;
/**
* 訂單類。
* 不知道用戶選擇的具體付款方式(策略)。
* 它使用公共策略接口將采集的支付數據委托給策略對象。
* 它可以用來保存訂單到數據庫。
*/
public class Order {
private int totalCost = 0;
private boolean isClosed = false;
public void processOrder(PayStrategy strategy) {
strategy.collectPaymentDetails();
}
public int getTotalCost() {
return totalCost;
}
public void setTotalCost(int totalCost) {
this.totalCost = totalCost;
}
public boolean isClosed() {
return isClosed;
}
public void setClosed() {
isClosed = true;
}
}
測試代碼
package behavioral.strategy;
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();
// Client creates different strategies based on input from user,
// application configuration, etc.
if (paymentMethod.equals("1")) {
strategy = new PayByPayPal();
} else {
strategy = new PayByCreditCard();
}
}
// Order object delegates gathering payment data to strategy object,
// since only strategies know what data they need to process a
// payment.
order.processOrder(strategy);
System.out.print("Pay " + order.getTotalCost() + " units or Continue shopping? P/C: ");
String proceed = reader.readLine();
if (proceed.equalsIgnoreCase("P")) {
// Finally, strategy handles the payment.
if (strategy.pay(order.getTotalCost())) {
System.out.println("Payment has been successful.");
} else {
System.out.println("FAIL! Please, check your data.");
}
order.setClosed();
}
}
}
}
/**
*Please, select a product:
* 1 - Mother board
* 2 - CPU
* 3 - HDD
* 4 - Memory
* 1
* Count: 3
* 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:
* hua@qq.com
* Enter the password:
* hua
* Data verification has been successful.
* Pay 6600 units or Continue shopping? P/C:
* P
* Paying 6600 using PayPal.
* Payment has been successful.
*
* Process finished with exit code 0
*/
適用場景
-
當你想使用對象中各種不同的算法變體, 並希望能在運行時切換算法時, 可使用策略模式。
策略模式讓你能夠將對象關聯至可以不同方式執行特定子任務的不同子對象, 從而以間接方式在運行時更改對象行為。
-
當你有許多僅在執行某些行為時略有不同的相似類時, 可使用策略模式。
策略模式讓你能將不同行為抽取到一個獨立類層次結構中, 並將原始類組合成同一個,從而減少重復代碼。
-
如果算法在上下文的邏輯中不是特別重要, 使用該模式能將類的業務邏輯與其算法實現細節隔離開來。
策略模式讓你能將各種算法的代碼、 內部數據和依賴關系與其他代碼隔離開來。 不同客戶端可通過一個簡單接口執行算法, 並能在運行時進行切換。
-
當類中使用了復雜條件運算符以在同一算法的不同變體中切換時, 可使用該模式。
策略模式將所有繼承自同樣接口的算法抽取到獨立類中, 因此不再需要條件語句。 原始對象並不實現所有算法的變體, 而是將執行工作委派給其中的一個獨立算法對象。
實現方式
- 從上下文類中找出修改頻率較高的算法 (也可能是用於在運行時選擇某個算法變體的復雜條件運算符)。
- 聲明該算法所有變體的通用策略接口。
- 將算法逐一抽取到各自的類中, 它們都必須實現策略接口。
- 在上下文類中添加一個成員變量用於保存對於策略對象的引用。 然后提供設置器以修改該成員變量。 上下文僅可通過策略接口同策略對象進行交互, 如有需要還可定義一個接口來讓策略訪問其數據。
- 客戶端必須將上下文類與相應策略進行關聯, 使上下文可以預期的方式完成其主要工作。
策略模式優點
- 你可以在運行時切換對象內的算法。
- 你可以將算法的實現和使用算法的代碼隔離開來。
- 你可以使用組合來代替繼承。
- 開閉原則。 你無需對上下文進行修改就能夠引入新的策略。
策略模式缺點
- 如果你的算法極少發生改變, 那么沒有任何理由引入新的類和接口。 使用該模式只會讓程序過於復雜。
- 客戶端必須知曉策略間的不同——它需要選擇合適的策略。
- 許多現代編程語言支持函數類型功能,允許你在一組匿名函數中實現不同版本的算法。 這樣, 你使用這些函數的方式就和使用策略對象時完全相同, 無需借助額外的類和接口來保持代碼簡潔。