Bootstrap

策略模式的实现

1. 引言:策略模式的启示

代码的高可维护性和扩展性一直是软件工程师的极致追求。试想一下,当我们负责开发一个系统时,可能会需要根据不同的用户职责和使用场景,动态地调整不同的行为策略。而随着系统的不断迭代更新,就会发现原有的代码开始变得异常臃肿,难以维护,如果后续新开了职位,就不得不修改大量的现有代码,这将极大的增加出错的风险,也降低了开发效率。

针对这种情况,这时候就需要策略模式大显身手了。策略模式是一种行为设计模式,它定义了一系列算法,并将每一个算法封装起来,使它们可以互换。这种模式让算法的变化独立于使用算法的客户端。通过使用策略模式,我们可以轻松地添加或修改策略,而不需要修改现有的代码。这不仅提高了代码的可维护性,还使得系统更加灵活和可扩展。

当然,策略模式的好处远不止于此。下面我们将会详细介绍策略模式的基本概念、实现步骤。通过本篇文章,你将能够理解策略模式的工作原理,并学会如何在实际项目中有效地利用它

2. 何为策略模式

可以打这么个比方,策略模式就像是在餐厅点餐的过程。假设你和朋友一起去餐厅,你们可以根据自己的口味选择不同的菜肴。你可以点披萨,你的朋友可以点汉堡,而另一位朋友可以点沙拉。每个人都可以根据自己的喜好选择不同的策略(菜肴),但最终都能满足吃东西的需求。

2.1 策略模式的主要角色

  1. 策略接口(Strategy Interface)  定义一个公共接口,所有具体的策略类都必须实现这个接口。就像是菜单上的菜肴类别,比如“主菜”、“甜点”等。它定义了你可以在餐厅点的东西的类型。
  2. 具体策略类(Concrete Strategy)  实现了策略接口,提供具体的算法实现。就像是菜单上具体的菜肴,比如“意大利披萨”、“牛肉汉堡”等。每种菜肴都有自己独特的做法和口味,但它们都实现了“主菜”这个策略接口。
  3. 上下文类(Context)  持有一个策略对象的引用,并提供一个方法来设置和使用这个策略对象。就像是服务员。当你点餐时,服务员会记录你点的菜肴,并确保它被正确地送到厨房制作。服务员并不关心菜肴的具体做法,他只是负责传递你的点餐需求。

2.2 策略模式的优点

通过这种方式,策略模式使得代码更加灵活和可扩展,就像在餐厅点餐一样,可以随时选择不同的菜肴,而不需要改变餐厅的菜单或服务流程。如同策略模式的优点一般:

  1. 算法封装:每个算法都被封装在一个独立的策略类中,便于维护和扩展。
  2. 易于切换算法:可以在运行时动态切换算法,而不需要修改客户端代码。
  3. 避免条件语句:策略模式可以避免使用大量的条件语句来选择不同的算法。

2.3 策略模式的缺点

当然,如果点餐时菜单上的菜肴太多,也会让人陷入应接不暇的窘境,且需要从中筛选出我们真实想要的菜肴类别,才能点到我们预期的菜品。就如策略模式的缺点一样:

  1. 增加类的数量:引入策略模式会增加类的数量,可能会增加系统的复杂性。
  2. 客户端需要了解策略:客户端需要了解不同的策略类,并选择合适的策略。

3. 策略模式的简单实现

以餐厅点餐为例,假设我们有一个餐厅点餐系统,顾客可以选择不同的菜肴,并且系统可以根据顾客的选择来准备相应的菜肴。

首先,我们需要定义一个策略接口,其包含一个方法,用来提供不同的菜肴。

public interface CookingStrategy {
   void cook();
}

接下来,我们来实现具体的策略类,每个类代表一种不同的菜肴。

public class PizzaCookingStrategy implements CookingStrategy {
    @Override
    public void cook() {
        System.out.println("可口的意大利披萨");
    }
}
public class BurgerCookingStrategy implements CookingStrategy {
    @Override
    public void cook() {
        System.out.println("美味的蟹堡王汉堡");
    }
}
public class SaladCookingStrategy implements CookingStrategy {
    @Override
    public void cook() {
        System.out.println("清爽的蔬菜沙拉");
    }
}

然后,定义一个上下文类,让它持有CookingStrategy对象,并提供一个方法来设置和使用这个策略对象。

public class Order {

    private CookingStrategy cookingStrategy;

    public void setCookingStrategy(CookingStrategy cookingStrategy) {
        this.cookingStrategy = cookingStrategy;
    }

    public void prepareDish() {
        if (cookingStrategy != null) {
            cookingStrategy.cook();
        } else {
            System.out.println("请选择烹饪策略");
        }
    }
}

最后,在客户端编写代码模拟顾客点餐的过程。

public class Restaurant {

    public static void main(String[] args) {
    
        Order order = new Order();

        // 顾客点了一份披萨
        CookingStrategy pizzaStrategy = new PizzaCookingStrategy();
        order.setCookingStrategy(pizzaStrategy);
        order.prepareDish();

        // 顾客又点了一份汉堡
        CookingStrategy burgerStrategy = new BurgerCookingStrategy();
        order.setCookingStrategy(burgerStrategy);
        order.prepareDish();

        // 顾客最后点了一份沙拉
        CookingStrategy saladStrategy = new SaladCookingStrategy();
        order.setCookingStrategy(saladStrategy);
        order.prepareDish();
    }
}

4. 策略模式在SpringBoot框架中的应用

学过SpringBoot的都知道,SpringBoot是一个非常庞大的框架,使用了多种设计模式,其中就包含了策略模式。我们可以简单的看一段SpringBoot框架中策略模式的使用。
org.springframework.boot.env.EnvironmentPostProcessor中,提供了一个策略接口EnvironmentPostProcessor,它的主要作用是在应用上下文刷新之前对Environment进行自定义处理。多个EnvironmentPostProcessor实现类可以被注册到SpringBoot应用中,每个实现类都可以对Environment进行不同的处理。

源码如下:

1. 策略接口

package org.springframework.boot.env;

import org.springframework.boot.SpringApplication;
import org.springframework.core.env.ConfigurableEnvironment;

@FunctionalInterface
public interface EnvironmentPostProcessor {
    void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application);
}

2. SpringBoot 提供了多个EnvironmentPostProcessor的具体策略类,如SpringApplicationJsonEnvironmentPostProcessor 等。

package org.springframework.boot.env;

public class SpringApplicationJsonEnvironmentPostProcessor implements EnvironmentPostProcessor, Ordered  {

    public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
        MutablePropertySources propertySources = environment.getPropertySources();
        propertySources.stream().map(JsonPropertyValue::get).filter(Objects::nonNull).findFirst().ifPresent((v) -> {
            this.processJson(environment, v);
        });
    }
}

3. SpringBoot SpringApplication 类充当了上下文角色,它在启动过程中会加载并调用所有注册的 EnvironmentPostProcessor 实现类。

package org.springframework.boot;

public class SpringApplication {

    private List<EnvironmentPostProcessor> environmentPostProcessors = new ArrayList<>();

    public void setEnvironmentPostProcessors(List<EnvironmentPostProcessor> environmentPostProcessors) {
        this.environmentPostProcessors = environmentPostProcessors;
    }

    public void run(String... args) {
        ConfigurableEnvironment environment = prepareEnvironment(args);
        for (EnvironmentPostProcessor postProcessor : environmentPostProcessors) {
            postProcessor.postProcessEnvironment(environment, this);
        }
        refreshContext(context);
    }
}

在这个例子中,EnvironmentPostProcessor 接口和它的实现类(如SpringApplicationJsonEnvironmentPostProcessor)是策略模式中的具体策略类。SpringApplication 类是上下文类,它持有一组 EnvironmentPostProcessor 实现类,并在启动过程中根据需要调用这些实现类。这种设计体现了策略模式的核心思想,即在运行时选择不同的算法或行为,而不需要改变使用这些算法或行为的代码。

4.1 使用依赖注入和Spring的配置实现策略模式

在Spring Boot中使用策略模式,可以通过依赖注入和Spring的配置来实现。让我们以餐厅点餐系统为例,展示如何在Spring Boot中应用策略模式。

假设我们有一个餐厅点餐系统,顾客可以选择不同的菜肴(策略),并且系统可以根据顾客的选择来准备相应的菜肴。

1. 定义策略接口

首先,我们定义一个策略接口 CookingStrategy,它包含一个 cook 方法,用于准备菜肴。

public interface CookingStrategy {
    void cook();
}

2. 实现具体策略类

接下来,我们实现具体的策略类,每个类代表一种不同的菜肴。

import org.springframework.stereotype.Component;

@Component("pizzaCookingStrategy")
public class PizzaCookingStrategy implements CookingStrategy {
    @Override
    public void cook() {
        System.out.println("可口的意大利披萨");
    }
}
import org.springframework.stereotype.Component;

@Component("burgerCookingStrategy")
public class BurgerCookingStrategy implements CookingStrategy {
    @Override
    public void cook() {
        System.out.println("美味的蟹堡王汉堡");
    }
}

3. 定义上下文类

然后,我们定义一个上下文类 Order,它持有一个 CookingStrategy 对象,并提供一个方法来设置和使用这个策略对象。

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;

@Service
public class Order {
    private CookingStrategy cookingStrategy;

    @Autowired
    public void setCookingStrategy(@Qualifier("pizzaCookingStrategy") CookingStrategy cookingStrategy) {
        this.cookingStrategy = cookingStrategy;
    }

    public void prepareDish() {
        if (cookingStrategy != null) {
            cookingStrategy.cook();
        } else {
            System.out.println("请选择烹饪策略");
        }
    }
}

4. 配置Spring Boot应用

在Spring Boot应用的主类中,启用组件扫描。

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class RestaurantApplication {
    public static void main(String[] args) {
        SpringApplication.run(RestaurantApplication.class, args);
    }
}

5. 客户端代码

最后,我们编写客户端代码来模拟顾客点餐的过程。

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;

@Component
public class RestaurantRunner implements CommandLineRunner {
    @Autowired
    private Order order;

    @Override
    public void run(String... args) throws Exception {
        order.prepareDish();
    }
}

5. 总结

通过策略模式,我们可以轻松地添加新的菜肴(策略),而不需要修改现有的代码。这使得系统更加灵活和易于扩展。顾客可以根据自己的喜好选择不同的菜肴,而餐厅的菜单(策略接口)和具体的菜肴(具体策略类)是固定的,但顾客可以根据自己的喜好(上下文)选择不同的菜肴。 这就是策略模式的核心思想:在运行时选择不同的算法或行为,而不需要改变使用这些算法或行为的代码。

;