当前位置: 首页 > article >正文

【策略方法】设计模式:构建灵活的算法替换方案

摘要

在软件开发中,经常需要根据不同的条件应用不同的算法或行为。策略模式提供了一种优雅的解决方案,允许在运行时根据不同的需求动态替换算法。

原理

策略模式是一种行为设计模式,主要解决“类或对象之间的交互问题”,通过定义一族算法类,将每个算法分别封装起来,让它们可以互相替换。策略模式可以使算法的变化独立于使用它们的客户端。

结构

  • 策略接口(Strategy):定义了一个公共的接口,所有的策略类都必须实现这个接口;
  • 具体策略类(Concrete Strategy):实现了策略接口的具体算法类;
  • 上下文(Context):使用策略接口作为其属性,可以配置并使用一个具体的策略类。

策略定义

策略类的定义:包含一个策略接口和一组实现这个接口的策略类。

public interface Strategy {
  void algorithmInterface();
}

public class ConcreteStrategyA implements Strategy {
  @Override
  public void  algorithmInterface() {
    //具体的算法...
  }
}

public class ConcreteStrategyB implements Strategy {
  @Override
  public void  algorithmInterface() {
    //具体的算法...
  }
}

策略的创建

根据类型创建策略的逻辑一般都放到工厂类中。

public class StrategyFactory {
  private static final Map<String, Strategy> strategies = new HashMap<>();

  static {
    strategies.put("A", new ConcreteStrategyA());
    strategies.put("B", new ConcreteStrategyB());
  }

  public static Strategy getStrategy(String type) {
    if (type == null || type.isEmpty()) {
      throw new IllegalArgumentException("type should not be empty.");
    }
    return strategies.get(type);
  }
}

如果策略类是无状态的,不包含成员变量,只是单纯的算法实现,这样的策略对象是可以被共享使用的,不需要在每次调用getStrategy时都去创建一个新的策略对象。
这样的情况,我们可以使用上面这种工厂类的实现方式实现创建好每个策略对象,缓存到工厂类中,用的时候直接取出返回。

如果策略类是有状态的,根据业务场景的需要,我们希望每次从工厂方法中,获得的都是新创建的策略对象,而不是缓存好可共享的策略对象,那么就需要以如下的方式来实现策略工厂类。

public class StrategyFactory {
  public static Strategy getStrategy(String type) {
    if (type == null || type.isEmpty()) {
      throw new IllegalArgumentException("type should not be empty.");
    }
    if (type.equals("A")) {
      return new ConcreteStrategyA();
    } else if (type.equals("B")) {
      return new ConcreteStrategyB();
    }
    return null;
  }
}

比如有一个计费策略接口BillingStrategy,有两个实现类NormalBillingStrategyDiscountBillingStrategy都需要记录当前的消费计数totalCount

public interface BillingStrategy {
    int getFinalPrice(int price, int totalCount);
}

//普通计费策略
public class NormalBillingStrategy implements BillingStrategy {
    private int totalCount;

    public NormalBillingStrategy(int totalCount) {
        this.totalCount = totalCount;
    }

    @Override
    public int getFinalPrice(int price, int totalCount) {
        return price * totalCount;
    }
}
//折扣计费策略
public class DiscountBillingStrategy implements BillingStrategy {
    private int totalCount;

    public DiscountBillingStrategy(int totalCount) {
        this.totalCount = totalCount;
    }

    @Override
    public int getFinalPrice(int price, int totalCount) {
        if (totalCount > 5) {
            return (int) (price * totalCount * 0.8); // 8折优惠
        } else {
            return price * totalCount;
        }
    }
}

我们希望每次调用getStrategy方法都会返回一个新的BillingStrategy实例,以确保每个策略对象的totalCount相互独立。因此可以使用下面的工厂方法来创建策略对象。

public class BillingStrategyFactory {
    public static BillingStrategy getStrategy(String type, int totalCount) {
        switch (type) {
            case "Normal":
                return new NormalBillingStrategy(totalCount);
            case "Discount":
                return new DiscountBillingStrategy(totalCount);
            default:
                throw new IllegalArgumentException("Invalid strategy type");
        }
    }
}

这样每次调用getStrategy方法都会创建一个新的BillingStrategy实例,以确保状态独立性。

BillingStrategy strategy1 = BillingStrategyFactory.getStrategy("Normal", 10);
BillingStrategy strategy2 = BillingStrategyFactory.getStrategy("Discount", 3);
strategy1.getFinalPrice(100,strategy1.totalCount)

策略的使用

最经典的场景:就是运行时动态确定使用哪种策略
所谓的“运行时动态”,指的是事先并不知道会使用哪种策略,而是在程序运行期间,根据配置、用户输入、计算结果这些不确定因素动态确定使用哪种策略。
比如,现在有一个策略接口EvictionStrategy,三个策略类LruEvictionStrategyFifoEvictionStrategyLfuEvictionStrategy,一个策略工厂EvictionStrategyFactory

运行时动态确定

比如有一个配置文件config.properties

eviction_type = lru
public class Application {
  public static void main(String[] args) throws Exception {
    EvictionStrategy evictionStrategy = null; //根据配置文件动态确定
    Properties props = new Properties();
    props.load(new FileInputStream("./config.properties"));
    String type = props.getProperty("eviction_type");
    evictionStrategy = EvictionStrategyFactory.getEvictionStrategy(type);
  }
}

在真实项目中,我们更多的应该是基于用户输入或者其他接口的返回结果来动态确定使用哪种策略。

非运行时动态确定
public class Application {
  public static void main(String[] args) {
    EvictionStrategy evictionStrategy = new LruEvictionStrategy(); //直接写死了
    //...
  }
}

从上面代码看出,“非运行时动态确定”并不能发挥策略模式的优势。在这种场景下,策略模式实际上退化成了“面向对象的多态特性”或“基于接口而非实现编程原则”。

移除分支判断

策略模式适用于根据不同类型的动态,决定使用哪种策略的应用场景。
比如,下面这段代码,没有使用策略模式。

public class OrderService {
  public double discount(Order order) {
    double discount = 0.0;
    OrderType type = order.getType();
    if (type.equals(OrderType.NORMAL)) { // 普通订单
      //...省略折扣计算算法代码
    } else if (type.equals(OrderType.GROUPON)) { // 团购订单
      //...省略折扣计算算法代码
    } else if (type.equals(OrderType.PROMOTION)) { // 促销订单
      //...省略折扣计算算法代码
    }
    return discount;
  }
}

那么怎么用策略模式来移除分支判断逻辑呢?
将不同类型订单的打折策略设计成策略类,且由工厂类来负责创建策略对象。

// 策略的定义
public interface DiscountStrategy {
  double calDiscount(Order order);
}
// 省略NormalDiscountStrategy、GrouponDiscountStrategy、PromotionDiscountStrategy类代码...

// 策略的创建
public class DiscountStrategyFactory {
  private static final Map<OrderType, DiscountStrategy> strategies = new HashMap<>();

  static {
    strategies.put(OrderType.NORMAL, new NormalDiscountStrategy());
    strategies.put(OrderType.GROUPON, new GrouponDiscountStrategy());
    strategies.put(OrderType.PROMOTION, new PromotionDiscountStrategy());
  }

  public static DiscountStrategy getDiscountStrategy(OrderType type) {
    return strategies.get(type);
  }
}

// 策略的使用
public class OrderService {
  public double discount(Order order) {
    OrderType type = order.getType();
    DiscountStrategy discountStrategy = DiscountStrategyFactory.getDiscountStrategy(type);
    return discountStrategy.calDiscount(order);
  }
}

但是,如果业务场景需要每次都创建不同的策略对象,那么就需要另一种工厂类的实现方式了。

public class DiscountStrategyFactory {
  public static DiscountStrategy getDiscountStrategy(OrderType type) {
    if (type == null) {
      throw new IllegalArgumentException("Type should not be null.");
    }
    if (type.equals(OrderType.NORMAL)) {
      return new NormalDiscountStrategy();
    } else if (type.equals(OrderType.GROUPON)) {
      return new GrouponDiscountStrategy();
    } else if (type.equals(OrderType.PROMOTION)) {
      return new PromotionDiscountStrategy();
    }
    return null;
  }
}

但是这种方式相当于把原来的if-else分支逻辑转移到了工厂类中,并没有真正的移除。那怎么解决呢?
我们可以通过反射来避免对策略工厂类的修改。具体步骤:

  • 通过一个配置文件或者自定义的annotation来标注都有哪些策略类;
  • 策略工厂读取类读取配置文件或搜索被annotation标注的策略类,然后通过反射动态的加载这些策略类,创建策略对象。
  • 当我们添加一个新策略时,只需要将这个新策略类添加到配置文件或使用annotation标注即可。
配置文件

定义配置文件strategies.properties

normal=com.xxx.NormalDiscountStrategy
groupon=com.xxx.GrouponDiscountStrategy
promotion=com.xxx.PromotionDiscountStrategy

策略工厂类 (DiscountStrategyFactory) 使用配置文件。

public class DiscountStrategyFactory {
    private static Properties properties = new Properties();

    static {
        try (FileInputStream fis = new FileInputStream("strategies.properties")) {
            properties.load(fis);
        } catch (IOException e) {
            throw new RuntimeException("Failed to load strategy configuration", e);
        }
    }

    public static DiscountStrategy getDiscountStrategy(OrderType type) {
        String className = properties.getProperty(type.toString());
        if (className == null) {
            throw new IllegalArgumentException("No strategy found for type: " + type);
        }
        try {
            Class<?> clazz = Class.forName(className);
            return (DiscountStrategy) clazz.getDeclaredConstructor().newInstance();
        } catch (Exception e) {
            throw new RuntimeException("Failed to create strategy instance for: " + className, e);
        }
    }
}
自定义注解

定义注解StrategyAnnotation

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface StrategyAnnotation {
    OrderType value();
}

定义类型枚举 OrderType

public enum OrderType {
    GROUP,
    NORMAL;
}

策略类使用注解

@StrategyAnnotation(OrderType.GROUPON)
public class GrouponDiscountStrategy implements DiscountStrategy {
    @Override
    public double calculateDiscount(double originalPrice) {
        return originalPrice * 0.8; // 20% discount
    }
}

// ... 其他策略类使用注解 ...

策略工厂类DiscountStrategyFactory

public class DiscountStrategyFactory {
    private static final Map<OrderType, DiscountStrategy> strategyMap = new HashMap<>();
    private static final Reflections reflections = new Reflections("strategy",
            new SubTypesScanner(false), new TypeAnnotationsScanner());

    static {
        Set<Class<? extends DiscountStrategy>> strategyClasses = reflections.getSubTypesOf(DiscountStrategy.class);
        for (Class<? extends DiscountStrategy> strategyClass : strategyClasses) {

            if (strategyClass.isAnnotationPresent(StrategyAnnotation.class)) {
                try {
                    StrategyAnnotation annotation = strategyClass.getAnnotation(StrategyAnnotation.class);
                    DiscountStrategy strategy = strategyClass.getDeclaredConstructor().newInstance();
                    strategyMap.put(annotation.value(), strategy);
                } catch (Exception e) {
                    throw new RuntimeException("Failed to instantiate strategy: " + strategyClass.getName(), e);
                }
            }
        }
    }

    // 根据类型获取策略算法类
    public static DiscountStrategy getStrategy(OrderType type) {
        return strategyMap.get(type);
    }
   
    // 使用
    public static void main(String[] args) {
        DiscountStrategy strategy = DiscountStrategyFactory.getStrategy(OrderType.NORMAL);
        System.out.println(strategy.calculateDiscount(2));
    }

}

总结

策略模式为算法的替换提供了一种灵活且可扩展的方式。通过将策略的实现与使用分离,我们可以根据不同的业务场景轻松地替换或扩展策略,而不需要修改现有的代码。


http://www.kler.cn/a/283645.html

相关文章:

  • Prometheus面试内容整理-Prometheus 的架构和工作原理
  • 网络安全-蓝队基础
  • 使用Matlab建立随机森林
  • Gurobi学术版+Anaconda安装步骤
  • SpringBoot后端解决跨域问题
  • Linux相关习题-gcc-gdb-冯诺依曼
  • 已经git push,但上传的文件超过100MB
  • 都2024了,还在为uniapp的app端tabbar页面闪烁闪白而发愁吗
  • AI:引领未来的科技浪潮
  • 基于vue框架的餐馆管理系统jo0i7(程序+源码+数据库+调试部署+开发环境)系统界面在最后面。
  • 解决Vite+Vue3打包项目本地运行html白屏和报错问题
  • 【iOS】Masonry学习
  • EasyCode实现完整CRUD + 分页封装
  • RateLimiter超时
  • Memcached stats items 命令
  • JVM运行时数据区详解
  • 全球视角下的AI应用:国内外技术与实践的比较分析
  • 了解一下 CSS 的了解font-variant-alternates属性
  • TCP/IP和SNMP
  • matlab峰值检测
  • HTML静态网页成品作业(HTML+CSS+JS)——迪士尼公主介绍(6个页面)
  • sql server导入mysql,使用python多线程
  • 从blob 下载zip文件到本地并解压
  • 罗德与施瓦茨RS、UPV 音频分析仪 250KHZ 双通道分析仪UPL
  • 【面试经验】字节产品经理二面面经
  • MySQL空间函数ST_Distance_Sphere()的使用