策略模式(學習筆記)

  1. 意圖

  定義一系列算法,把它們一個個封裝起來,而且使它們可相互替換。本模式使得算法可獨立於使用它的客戶而變化java

  2. 動機   

  假設打算爲遊客們建立一款導遊程序。該程序的核心功能是提供美觀的地圖,以幫助用戶在任何城市中快速定位。用戶期待的程序新功能是自動路線規劃:他們但願輸入地址後就能在地圖上看到前往目的地的最快路線。程序的首個版本只能規劃公路路線。駕車旅行的人們對此很是滿意。但很顯然,並不是全部人都會在度假時開車。所以在下次更新時添加了規劃步行路線的功能。此後,又添加了規劃公共交通路線的功能。而這只是個開始。不久後,又要爲騎行者規劃路線。又過了一段時間,又要爲遊覽城市中的全部景點規劃路線。儘管從商業角度來看,這款應用很是成功,但其技術部分卻讓你很是頭疼:每次添加新的路線規劃算法後,導遊應用中主要類的體積就會增長一倍。隨着需求的不斷增長,你以爲本身無法繼續維護這堆代碼了算法

  策略模式經過定義一些類來封裝不一樣功能背後的算法,從而避免了一個類不斷膨脹以致於難以維護的問題。名爲上下文的原始類必須包含一個成員變量來存儲對於每種策略的引用。上下文並不執行任務,而是將工做委派給已鏈接的策略對象。上下文不負責選擇符合任務須要的算法——客戶端會將所需策略傳遞給上下文。實際上,上下文並不十分了解策略,它會經過一樣的通用接口與全部策略進行交互,而該接口只需暴露一個方法來觸發所選策略中封裝的算法便可。所以,上下文可獨立於具體策略。這樣你就可在不修改上下文代碼或其餘策略的狀況下添加新算法或修改已有算法了。在導遊應用中,每一個路線規劃算法均可被抽取到 buildRoute 方法的獨立類中。 該方法接收起點和終點做爲參數,並返回路線中途點的集合。編程

          

  3. 適用性

  • 許多相關的類僅僅是行爲有異。「策略」提供了一種用多個行爲中的一個行爲來配置一個類的方法
  • 須要使用一個算法的不一樣變體
  • 算法使用客戶不該該知道的數據。可以使用策略模式以免暴露覆雜的、與算法相關的數據結構
  • 一個類定義了多種行爲,而且這些行爲在這個類的操做中以多個條件語句的形式出現。將相關的條件分支移入它們各自的Strategy類中以代替這些條件語句

  4. 結構

           

  5. 效果

  1. 能夠在運行時切換對象內的算法數據結構

  2. 一個替代繼承的方法       若是直接生成一個Context類的子類,從而給它以不一樣的行爲。這會將行爲硬性編制到Context中,將算法的實現與Context的實現混合起來,從而使Context難以理解、維護和擴展,並且還不能動態的改變算法app

  3. 消除了一些條件語句   框架

  4. Strategy能夠提供相同行爲的不一樣實現編程語言

  5. 客戶必須瞭解不一樣的Strategy以選擇合適的算法ide

  6. 許多現代編程語言支持函數類型功能,容許你在一組匿名函數中實現不一樣版本的算法。使用這些函數的方式就和使用策略對象時徹底相同,無需藉助額外的類和接口來保持代碼簡潔函數

  7. 增長了對象的數目ui

  8. Strategy和Context之間的通訊開銷      不管各個ConcreteStrategy實現的算法是簡單仍是複雜,它們都共享Strategy定義的接口。所以極可能某些ConcreteStrategy不會用到全部經過這個接口傳遞給它們的信息,簡單的ConcreteStrategy可能不使用其中的任何信息!這就意味着有時Context會建立和初始化一些永遠不會用到的參數。若是存在這個問題,須要在Strategy和Context之間進行更緊密的耦合

  6. 代碼實現  

  在本例中,策略模式被用於在電子商務應用中實現各類支付方法。客戶選中但願購買的商品後須要選擇一種支付方式:Paypal 或者信用卡。具體策略不只會完成實際的支付工做,還會改變支付表單的行爲,並在表單中提供相應的字段來記錄支付信息

  strategies/PayStrategy.java: 通用的支付方法接口

package strategy.strategies;

/**
 * @author GaoMing
 * @date 2021/7/26 - 20:57
 * Common interface for all strategies.
 */
public interface PayStrategy {
    boolean pay(int paymentAmount);
    void collectPaymentDetails();
}

  strategies/PayByPayPal.java: 使用 PayPal 支付

package strategy.strategies;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.HashMap;
import java.util.Map;

/**
 * @author GaoMing
 * @date 2021/7/26 - 20:57
 */
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: 使用信用卡支付

package strategy.strategies;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;

/**
 * @author GaoMing
 * @date 2021/7/26 - 20:58
 */
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: 信用卡類

package strategy.strategies;

/**
 * @author GaoMing
 * @date 2021/7/26 - 20:58
 */
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;
    }
}

  context/Order.java: 訂單類

package strategy.context;

import strategy.strategies.PayStrategy;

/**
 * @author GaoMing
 * @date 2021/7/26 - 20:59
 * 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: 客戶端代碼

package strategy;

import strategy.context.Order;
import strategy.strategies.PayByCreditCard;
import strategy.strategies.PayByPayPal;
import strategy.strategies.PayStrategy;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.HashMap;
import java.util.Map;

/**
 * @author GaoMing
 * @date 2021/7/26 - 20:56
 */
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: 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.   

  7. 與其餘模式的關係

  • 橋接模式、狀態模式和策略模式(在某種程度上包括適配器模式)模式的接口很是類似。實際上,它們都基於組合模式——即將工做委派給其餘對象,不過也各自解決了不一樣的問題。模式並不僅是以特定方式組織代碼的配方,你還可使用它們來和其餘開發者討論模式所解決的問題 
  • 命令模式和策略看上去很像,由於二者都能經過某些行爲來參數化對象。可是,它們的意圖有很是大的不一樣:
    - 命令模式將任何操做轉換爲對象。 操做的參數將成爲對象的成員變量。能夠經過轉換來延遲操做的執行、將操做放入隊列、保存歷史命令或者向遠程服務發送命令等
    - 策略模式一般用於描述完成某件事的不一樣方式,讓你可以在同一個上下文類中切換算法

  • 裝飾模式可以讓你更改對象的外表,策略則讓你可以改變其本質
  • 模板方法模式基於繼承機制:它容許你經過擴展子類中的部份內容來改變部分算法。策略基於組合機制:你能夠經過對相應行爲提供不一樣的策略來改變對象的部分行爲。模板方法在類層次上運做,所以它是靜態的。策略在對象層次上運做,所以容許在運行時切換行爲
  • 狀態可被視爲策略的擴展。二者都基於組合機制:它們都經過將部分工做委派給「幫手」對象來改變其在不一樣情景下的行爲。策略使得這些對象相互之間徹底獨立,它們不知道其餘對象的存在。但狀態模式沒有限制具體狀態之間的依賴,且容許它們自行改變在不一樣情景下的狀態

  8. 已知應用

  策略模式在Java代碼中很常見。它常常在各類框架中使用,能在不擴展類的狀況下向用戶提供改變其行爲的方式  Java 8 開始支持 lambda 方法,它可做爲一種替代策略模式的簡單方式  一些核心 Java 程序庫中策略模式的示例:  對 java.util.Comparator#compare() 的調用來自 Collections#sort()  javax.servlet.http.HttpServlet:service­()方法,還有全部接受 Http­Servlet­Request和 Http­Servlet­Response對象做爲參數的 do­XXX()方法  javax.servlet.Filter#doFilter()  識別方法:策略模式能夠經過容許嵌套對象完成實際工做的方法以及容許將該對象替換爲不一樣對象的設置器來識別。

相關文章
相關標籤/搜索