引言
策略是一种行为设计模式, 它将一组行为转换为对象, 并使其在原始上下文对象内部能够相互替换。
原始对象被称为上下文, 它包含指向策略对象的引用并将执行行为的任务分派给策略对象。 为了改变上下文完成其工作的方式, 其他对象可以使用另一个对象来替换当前链接的策略对象。
Java语言
使用示例: 策略模式在 Java 代码中很常见。 它经常在各种框架中使用, 能在不扩展类的情况下向用户提供改变其行为的方式。
Java 8 开始支持 lambda 方法, 它可作为一种替代策略模式的简单方式。
这里有一些核心 Java 程序库中策略模式的示例:
- 对java.util.Comparator#fcompare()的调用来自 Collections#sort()。
- javax.servlet.http.HttpServlet: service()方法,还有所有接受 HttpServletRequest和 HttpservletResponse对象作为参数的 doxxX()方法。
- javax.servlet.Filter#doFilter
识别方法: 策略模式可以通过允许嵌套对象完成实际工作的方法以及允许将该对象替换为不同对象的设置器来识别。
电子商务应用中的支付方法
在本例中, 策略模式被用于在电子商务应用中实现各种支付方法。 客户选中希望购买的商品后需要选择一种支付方式: Paypal 或者信用卡。
具体策略不仅会完成实际的支付工作, 还会改变支付表单的行为, 并在表单中提供相应的字段来记录支付信息。
strategies
strategies/PayStrategy.java: 通用的支付方法接口
/**
* Common interface for all strategies.
*/
public interface PayStrategy {
boolean pay(int paymentAmount);
void collectPaymentDetails();
}
strategies/PayByPayPal.java: 使用 PayPal 支付
/**
* Concrete strategy. Implements PayPal payment method.
*/
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");
}
/**
* Collect customer's data.
*/
@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;
}
/**
* Save customer data for future shopping attempts.
*/
@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: 使用信用卡支付
/**
* Concrete strategy. Implements credit card payment method.
*/
public class PayByCreditCard implements PayStrategy {
private final BufferedReader READER = new BufferedReader(new InputStreamReader(System.in));
private CreditCard card;
/**
* Collect credit card data.
*/
@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);
// Validate credit card number...
} catch (IOException ex) {
ex.printStackTrace();
}
}
/**
* After card validation we can charge customer's credit 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;
}
}
private boolean cardIsPresent() {
return card != null;
}
}
strategies/CreditCard.java: 信用卡类
/**
* Dummy credit card class.
*/
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.java: 订单类
/**
* Order class. Doesn't know the concrete payment method (strategy) user has
* picked. It uses common strategy interface to delegate collecting payment data
* to strategy object. It can be used to save order to database.
*/
public class Order {
private int totalCost = 0;
private boolean isClosed = false;
public void processOrder(PayStrategy strategy) {
strategy.collectPaymentDetails();
// Here we could collect and store payment data from the strategy.
}
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: 客户端代码
/**
* World first console e-commerce application.
*/
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();
}
}
}
}
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.
GO语言
概念示例
思考一下构建内存缓存的情形。 由于处在内存中, 故其大小会存在限制。 在达到其上限后, 一些条目就必须被移除以留出空间。 此类操作可通过多种算法进行实现。 一些流行的算法有:
- 最少最近使用 (LRU): 移除最近使用最少的一条条目。
- 先进先出 (FIFO): 移除最早创建的条目。
- 最少使用 (LFU): 移除使用频率最低一条条目。
问题在于如何将我们的缓存类与这些算法解耦, 以便在运行时更改算法。 此外, 在添加新算法时, 缓存类不应改变。
这就是策略模式发挥作用的场景。 可创建一系列的算法, 每个算法都有自己的类。 这些类中的每一个都遵循相同的接口, 这使得系列算法之间可以互换。 假设通用接口名称为 evictionAlgo
移除算法 。
现在, 我们的主要缓存类将嵌入至 evictionAlgo
接口中。 缓存类会将全部类型的移除算法委派给 evictionAlgo
接口, 而不是自行实现。 鉴于 evictionAlgo
是一个接口, 我们可在运行时将算法更改为 LRU、 FIFO 或者 LFU, 而不需要对缓存类做出任何更改。
evictionAlgo.go: 策略接口
package main
type EvictionAlgo interface {
evict(c *Cache)
}
fifo.go: 具体策略
package main
import "fmt"
type Fifo struct {
}
func (l *Fifo) evict(c *Cache) {
fmt.Println("Evicting by fifo strtegy")
}
lru.go: 具体策略
package main
import "fmt"
type Lru struct {
}
func (l *Lru) evict(c *Cache) {
fmt.Println("Evicting by lru strtegy")
}
lfu.go: 具体策略
package main
import "fmt"
type Lfu struct {
}
func (l *Lfu) evict(c *Cache) {
fmt.Println("Evicting by lfu strtegy")
}
cache.go: 背景
package main
type Cache struct {
storage map[string]string
evictionAlgo EvictionAlgo
capacity int
maxCapacity int
}
func initCache(e EvictionAlgo) *Cache {
storage := make(map[string]string)
return &Cache{
storage: storage,
evictionAlgo: e,
capacity: 0,
maxCapacity: 2,
}
}
func (c *Cache) setEvictionAlgo(e EvictionAlgo) {
c.evictionAlgo = e
}
func (c *Cache) add(key, value string) {
if c.capacity == c.maxCapacity {
c.evict()
}
c.capacity++
c.storage[key] = value
}
func (c *Cache) get(key string) {
delete(c.storage, key)
}
func (c *Cache) evict() {
c.evictionAlgo.evict(c)
c.capacity--
}
main.go: 客户端代码
package main
func main() {
lfu := &Lfu{}
cache := initCache(lfu)
cache.add("a", "1")
cache.add("b", "2")
cache.add("c", "3")
lru := &Lru{}
cache.setEvictionAlgo(lru)
cache.add("d", "4")
fifo := &Fifo{}
cache.setEvictionAlgo(fifo)
cache.add("e", "5")
}
output.txt: 执行结果
Evicting by lfu strtegy
Evicting by lru strtegy
Evicting by fifo strtegy