Bootstrap

设计模式学习之——策略模式

策略模式(Strategy Pattern)是一种行为型设计模式,它允许定义一系列算法,并将每个算法封装在独立的类中,使它们可以互相替换。策略模式通过将算法的使用与算法的实现分离,使得算法可以独立于客户端而变化。

一、策略模式的核心思想

策略模式的核心思想是面向接口编程,而不是面向实现编程。它通过将算法封装成独立的类,使得可以在运行时动态地选择和切换算法,从而提高代码的可维护性、扩展性和复用性。

二、策略模式的主要角色

在策略模式结构图中,通常包含以下几个角色:

  1. 策略接口(Strategy):定义了一个或多个策略算法的方法,这些方法是所有具体策略类必须实现的。策略接口使得算法可以独立于使用它的客户而变化。
  2. 具体策略类(Concrete Strategies):实现了策略接口,提供了具体的算法实现。每个具体策略类都对应一种具体的算法。
  3. 上下文类(Context):持有一个策略接口的引用,在客户端调用上下文类的某个方法时,上下文类会把请求委托给当前持有的策略对象。上下文类通常还包含一个设置策略对象的方法,以便在运行时动态地切换策略。

三、策略模式的使用场景

策略模式通常适用于以下场景:

  1. 多算法选择:当需要在运行时根据情况选择不同的算法时,可以使用策略模式。例如,对于排序算法,根据数据量的不同可能选择快速排序、冒泡排序或插入排序等。
  2. 消除条件分支:当代码中存在大量的条件分支语句,并且这些条件分支都是根据相同的输入来选择不同的行为时,可以考虑使用策略模式来消除这些条件分支。这可以提高代码的可读性和可维护性。
  3. 算法的封装和复用:当系统中存在多个类似的算法,但它们的实现细节不同时,可以将这些算法封装成独立的策略类,以便复用和维护。
  4. 可扩展性:当需要为系统提供一种灵活、可拓展的方式来添加新的算法或行为时,策略模式可以帮助实现这一点,而无需修改现有的代码。
  5. 单一职责原则:当需要遵循单一职责原则,即每个类应该只负责一种功能时,策略模式可以将不同的算法分离到单独的策略类中,使得每个类都专注于一种算法。

四、策略模式的优缺点

优点:

  1. 提高了代码的灵活性和可维护性:通过策略模式,可以在运行时动态地选择和切换算法,而不需要修改客户端代码。这使得代码更加灵活和易于维护。
  2. 简化了单元测试:由于策略模式将算法封装在独立的类中,因此可以针对每个算法进行单独的单元测试,从而简化了测试过程。
  3. 遵循了开闭原则:策略模式可以在不修改现有代码的情况下添加新的算法或行为,这符合开闭原则的要求。

缺点:

  1. 策略类数量多:当算法的数量较多时,会导致策略类的数量增加,从而增加了系统的复杂性。
  2. 客户端需要了解策略:客户端代码需要知道有哪些策略可供选择,并创建相应的策略对象,这可能会增加客户端的复杂度。

五、策略模式的示例

以下是几个简单的策略模式示例。

示例一:促销活动

  • 满减促销:当顾客购买一定金额的商品后,可以直接减去相应的金额。
  • 返现促销:在顾客购买商品后,返回一定比例的现金或优惠券。
  • 打折促销:对选定商品或全部商品进行打折处理。
  • 买赠促销:购买特定商品时赠送其他商品或服务
// 促销策略接口
public interface PromotionStrategy {
    double calculatePrice(double originalPrice);
}

// 满减策略
public class FullReductionStrategy implements PromotionStrategy {
    private double threshold;
    private double reduction;

    public FullReductionStrategy(double threshold, double reduction) {
        this.threshold = threshold;
        this.reduction = reduction;
    }

    @Override
    public double calculatePrice(double originalPrice) {
        if (originalPrice >= threshold) {
            return originalPrice - reduction;
        }
        return originalPrice;
    }
}

// 打折策略
public class DiscountStrategy implements PromotionStrategy {
    private double discountRate;

    public DiscountStrategy(double discountRate) {
        this.discountRate = discountRate;
    }

    @Override
    public double calculatePrice(double originalPrice) {
        return originalPrice * discountRate;
    }
}

// 上下文类
public class ShoppingCart {
    private PromotionStrategy promotionStrategy;

    public void setPromotionStrategy(PromotionStrategy promotionStrategy) {
        this.promotionStrategy = promotionStrategy;
    }

    public double getTotalPrice(double originalPrice) {
        return promotionStrategy.calculatePrice(originalPrice);
    }
}

// 客户端代码
public class PromotionDemo {
    public static void main(String[] args) {
        ShoppingCart cart = new ShoppingCart();

        // 使用满减策略
        cart.setPromotionStrategy(new FullReductionStrategy(100, 20));
        System.out.println("满减后价格: " + cart.getTotalPrice(120)); // 输出: 100.0

        // 使用打折策略
        cart.setPromotionStrategy(new DiscountStrategy(0.8));
        System.out.println("打折后价格: " + cart.getTotalPrice(120)); // 输出: 96.0
    }
}

示例二:排序与搜索 

  • 数组排序:根据不同需求,可以选择快速排序、归并排序、堆排序等算法进行数组排序。
  • 数据搜索:在大量数据中搜索目标值时,可以采用二分搜索、线性搜索等不同算法。
// 排序策略接口
public interface SortStrategy {
    void sort(int[] array);
}

// 快速排序策略
public class QuickSortStrategy implements SortStrategy {
    @Override
    public void sort(int[] array) {
        // 快速排序的具体实现
        // ...
        // 这里为了简化,省略了具体实现
        System.out.println("执行快速排序");
    }
}

// 冒泡排序策略
public class BubbleSortStrategy implements SortStrategy {
    @Override
    public void sort(int[] array) {
        // 冒泡排序的具体实现
        // ...
        // 这里为了简化,省略了具体实现
        System.out.println("执行冒泡排序");
    }
}

// 上下文类
public class Sorter {
    private SortStrategy sortStrategy;

    public void setSortStrategy(SortStrategy sortStrategy) {
        this.sortStrategy = sortStrategy;
    }

    public void sortArray(int[] array) {
        sortStrategy.sort(array);
    }
}

// 客户端代码
public class SortDemo {
    public static void main(String[] args) {
        int[] array = {5, 3, 8, 4, 2};
        Sorter sorter = new Sorter();

        // 使用快速排序
        sorter.setSortStrategy(new QuickSortStrategy());
        sorter.sortArray(array);

        // 使用冒泡排序
        sorter.setSortStrategy(new BubbleSortStrategy());
        sorter.sortArray(array);
    }
}

示例三:支付方式

  • 信用卡支付:使用信用卡结算,支持各种主流信用卡。
  • 第三方支付:如支付宝、微信支付等,满足不同用户习惯。
  • 货到付款:在收货时付款,提高用户信任度。
// 策略接口
public interface PaymentStrategy {
    void pay(int amount);
}

// 具体策略类:信用卡支付
public class CreditCardPayment implements PaymentStrategy {
    private String cardNumber;

    public CreditCardPayment(String cardNumber) {
        this.cardNumber = cardNumber;
    }

    public void pay(int amount) {
        System.out.println(amount + " paid with credit card.");
    }
}

// 具体策略类:PayPal支付
public class PayPalPayment implements PaymentStrategy {
    private String emailId;

    public PayPalPayment(String email) {
        this.emailId = email;
    }

    public void pay(int amount) {
        System.out.println(amount + " paid using PayPal.");
    }
}

// 上下文类
public class ShoppingCart {
    private PaymentStrategy paymentStrategy;

    public void setPaymentStrategy(PaymentStrategy paymentStrategy) {
        this.paymentStrategy = paymentStrategy;
    }

    public void checkout(int amount) {
        paymentStrategy.pay(amount);
    }
}

// 客户端代码
public class StrategyPatternExample {
    public static void main(String[] args) {
        ShoppingCart cart = new ShoppingCart();
        cart.setPaymentStrategy(new CreditCardPayment("1234567890123456"));
        cart.checkout(100);
        cart.setPaymentStrategy(new PayPalPayment("[email protected]"));
        cart.checkout(200);
    }
}

示例阶段小总结:(后面的示例将不再编写具体代码,参考方法套用实现即可)

定义策略接口:

首先,你需要定义一个策略接口,这个接口将声明所有支持的策略算法所共有的方法。这个接口就是算法的家族,为一系列具体的策略类提供了统一的接口

// 策略接口
public interface Strategy {
    void execute(); // 声明算法的方法
}

实现具体策略类:

然后,你需要为每一个具体的算法实现一个具体的策略类,这些类都实现了策略接口。每个具体策略类都提供了一个具体的算法实现。

// 具体策略类A
public class ConcreteStrategyA implements Strategy {
    public void execute() {
        // 算法A的具体实现
        System.out.println("执行算法A");
    }
}

// 具体策略类B
public class ConcreteStrategyB implements Strategy {
    public void execute() {
        // 算法B的具体实现
        System.out.println("执行算法B");
    }
}

创建上下文类:

上下文类持有一个策略接口的引用,它维护一个对具体策略的引用。客户端代码通过调用上下文类的方法来间接调用具体策略的方法。上下文类还可以提供一个方法来设置当前使用的策略。

// 上下文类
public class Context {
    private Strategy strategy;

    // 设置当前策略
    public void setStrategy(Strategy strategy) {
        this.strategy = strategy;
    }

    // 执行当前策略的方法
    public void executeStrategy() {
        strategy.execute();
    }
}

客户端代码:

最后,在客户端代码中,你可以根据需要动态地设置和切换策略。客户端代码通过创建上下文对象,并设置具体的策略来实现这一点。

// 客户端代码
public class StrategyPatternDemo {
    public static void main(String[] args) {
        Context context = new Context();

        // 使用算法A
        context.setStrategy(new ConcreteStrategyA());
        context.executeStrategy();

        // 使用算法B
        context.setStrategy(new ConcreteStrategyB());
        context.executeStrategy();
    }
}
关键点总结
  • 策略接口:定义了所有策略算法所共有的方法。
  • 具体策略类:实现了策略接口,提供了具体的算法实现。
  • 上下文类:持有一个策略接口的引用,通过调用该接口的方法来间接调用具体策略的方法。
  • 客户端代码:通过创建上下文对象,并设置具体的策略来实现算法的动态选择和切换。

示例四:数据压缩

  • 无损压缩:如zip、gzip等,保证数据完整性。
  • 有损压缩:如jpeg、mp3等,适用于图片和音频数据。

示例五:游戏开发

  • 敌人行为:根据不同关卡和难度,切换敌人的行为策略。
  • 玩家辅助:根据玩家等级提供不同的辅助策略,如自动瞄准、提示等。

示例六:出行选择

  • 出行方式的选择,如乘坐飞机、火车、自行车等,可以根据天气、距离、时间紧迫等因素决定采用哪一种方式出行。

示例七:表单验证

  • 一个表单验证工具可以根据不同的验证规则采用不同的验证策略,例如长度验证、格式验证等。

示例八:个人所得税计算

  • 不同国家或地区的个人所得税计算方法可能不同,策略模式可以使得这些不同的计算方法可以相互替换。

示例九:动物叫声模拟

  • 可以模拟不同动物的叫声,如狗的汪汪叫、猫的喵喵叫、鸟的啾啾叫等。

通过策略模式,可以将算法或行为与具体的业务逻辑解耦,使得系统更加灵活和可扩展。它允许在运行时动态地选择和切换算法,而无需修改原有的代码,从而提高了代码的复用性和可维护性。同时,策略模式也符合开闭原则,即对扩展开放,对修改关闭,使得系统可以更容易地应对未来可能的需求变化。

悦读

道可道,非常道;名可名,非常名。 无名,天地之始,有名,万物之母。 故常无欲,以观其妙,常有欲,以观其徼。 此两者,同出而异名,同谓之玄,玄之又玄,众妙之门。

;