返回 导航

其他

hangge.com

Java - GoF设计模式详解22(策略模式)

作者:hangge | 2023-07-04 10:32

二十二、策略模式

1,基本介绍

(1)策略模式(Strategy)定义一系列的算法,把它们封装起来,并且使它们可相互替换。策略模式使得算法可独立于使用它的用户而变化。
(1)策略模式与桥接模式的区别
  • 策略模式:是行为型模式,它并不考虑 Context 的变化,只有算法的可替代性。
  • 桥接模式:是结构型模式,不仅 Implementor 具有变化(ConcreteImplementor),而且 Abstraction 也可以发生变化(RefinedAbstraction),而且两者的变化是完全独立的,RefinedAbstractionConcreateImplementor 之间松散耦合,它们仅仅通过AbstractionImplementor 之间的关系联系起来。强调 Implementor 接口仅提供基本操作,而 Abstraction 则基于这些基本操作定义更高层次的操作。
(2)如果以画圆为例比较两种模式:
  • 策略模式:策略只是考虑算法的替换。我们要画实心圆,可以用 solidPen 来配置,画虚线圆可以用 dashedPen 来配置。。
  • 桥接模式:是不同平台下需要调用不同的工具,接口只是定义一个方法,而具体实现则由具体实现类完成。我是在 windows 下来画实心圆,就用 windowPen + solidPen 来配置,在 unix 下画实心圆就用 unixPen solidPen 来配置。如果要再 windows 下画虚线圆,就用 windowsPendashedPen 来配置,要在 unix 下画虚线圆,就用 unixPendashedPen 来配置。

(2)该模式中包含的角色及其职责如下:
  • 抽象策略角色Strategy): 定义了策略类的公共接口。
  • 具体策略角色ConcreteStrategy): 实现了抽象策略角色中的算法。
  • 环境角色Context): 持有一个策略类的引用,最终给客户端调用。

2,使用样例

(1)下面以支付策略的选择为例演示策略模式的使用。首先我们定义一个支付策略接口 PaymentStrategy,代码如下:
// 支付策略接口
interface PaymentStrategy {
  // 支付
  void pay(int amount);
}

(2)接着实现该接口定义两个具体的策略实现类,一个是使用信用卡支付的 CreditCardStrategy 类:
// 信用卡支付策略
class CreditCardStrategy implements PaymentStrategy {
  // 用户姓名
  private String name;
  // 卡号
  private String cardNumber;
  // 服务约束代码
  private String cvv;
  // 有效期
  private String dateOfExpiry;

  public CreditCardStrategy(String nm, String ccNum, String cvv, String expiryDate){
    this.name=nm;
    this.cardNumber=ccNum;
    this.cvv=cvv;
    this.dateOfExpiry=expiryDate;
  }
  @Override
  public void pay(int amount) {
    System.out.println("使用信用卡进行支付,金额:" + amount);
  }
}
// 支付宝支付策略
class AlipayStrategy implements PaymentStrategy {
  // 邮箱
  private String email;
  // 密码
  private String password;

  public AlipayStrategy(String email, String password) {
    this.email = email;
    this.password = password;
  }
  @Override
  public void pay(int amount) {
    System.out.println("使用支付宝进行支付,金额:" + amount);
  }
}

(3)然后定义一个购物车类 ShoppingCart,该类是一个环境角色,它持有一个 PaymentStrategy 类型的引用并调用其 pay() 方法来完成支付。
// 购物车类
class ShoppingCart {
  // 购物车内商品列表
  private ArrayList<Item> items;

  // 支付策略
  private PaymentStrategy paymentStrategy;

  public ShoppingCart(){
    this.items=new ArrayList<Item>();
  }

  // 计算总金额
  public int calculateTotal(){
    int sum = 0;
    for(Item item : items){
      sum += item.getPrice();
    }
    return sum;
  }

  // 支付
  public void pay(){
    int amount = calculateTotal();
    this.paymentStrategy.pay(amount);
  }

  public void addItem(Item item){
    this.items.add(item);
  }

  public void removeItem(Item item){
    this.items.remove(item);
  }

  public PaymentStrategy getPaymentStrategy() {
    return paymentStrategy;
  }

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

(4)最后测试一下,通过策略模式的使用,我们可以在运行时动态地更改支付策略,而不需要修改 ShoppingCart 类的代码。
public class Test {
  public static void main(String[] args) {
    // 创建购物车
    ShoppingCart cart = new ShoppingCart();
    // 往购物车中添加两件商品
    cart.addItem(new Item("牙刷", 10));
    cart.addItem(new Item("沐浴露", 22));

    // 设置支付方式为信用卡,并支付
    cart.setPaymentStrategy(new CreditCardStrategy("hangge", "127341", "786", "12/15"));
    cart.pay();

    // 设置支付方式为支付宝,并支付
    cart.setPaymentStrategy(new AlipayStrategy("hangge@hangge.com", "123456"));
    cart.pay();
  }
}

附一:JDK 中的策略模式

1,Collections 类与 Comparator 接口

(1)在 JDK 中的 java.util.Collections 类也使用了策略模式,它提供了一组静态方法,用于对集合进行排序和搜索操作。这些方法接受一个 Comparator 参数,用于指定排序或搜索策略。

(2)下面通过对员工集合数据排序进行演示。首先定义如下员工类 Employee
// 员工类
class Employee {
  private String name;
  private int age;
  private double salary;

  public Employee(String name, int age, double salary) {
    this.name = name;
    this.age = age;
    this.salary = salary;
  }

  public String getName() {
    return name;
  }

  public int getAge() {
    return age;
  }

  public double getSalary() {
    return salary;
  }

  @Override
  public String toString() {
    return "Employee [name=" + name + ", age=" + age + ", salary=" + salary + "]";
  }
}

(3)通过实现 Comparator 接口并重写 compare 方法,我们可以为不同的类型定义不同的比较策略。我们这里定义了三个类 EmployeeNameComparatorEmployeeAgeComparatorEmployeeSalaryComparator,分别实现了 Comparator 接口,定义了三种对象之间的比较策略。
// 使用用户名进行比较策略
class EmployeeNameComparator implements Comparator<Employee> {
  @Override
  public int compare(Employee e1, Employee e2) {
    return e1.getName().compareTo(e2.getName());
  }
}

// 使用用户年龄进行比较策略
class EmployeeAgeComparator implements Comparator<Employee> {
  @Override
  public int compare(Employee e1, Employee e2) {
    return e1.getAge() - e2.getAge();
  }
}

// 使用工资进行比较策略
class EmployeeSalaryComparator implements Comparator<Employee> {
  @Override
  public int compare(Employee e1, Employee e2) {
    return Double.compare(e1.getSalary(), e2.getSalary());
  }
}

(4)最后测试一下,我们创建了一个员工列表,并添加了几个员工对象。然后我们使用 Collections.sort 方法对员工列表进行排序,并在排序时传入了不同的比较器对象(EmployeeNameComparatorEmployeeAgeComparatorEmployeeSalaryComparator)。通过使用不同的比较器,我们可以在同一个程序中使用多种排序策略。
public class Test {
  public static void main(String[] args) {
    List<Employee> employees = new ArrayList<>();
    employees.add(new Employee("小刘", 30, 35000));
    employees.add(new Employee("小李", 25, 32000));
    employees.add(new Employee("小王", 40, 40000));
    employees.add(new Employee("小周", 35, 37000));

    // 根据姓名进行排序
    Collections.sort(employees, new EmployeeNameComparator());
    System.out.println("根据姓名进行排序: " + employees);

    // 根据年龄进行排序
    Collections.sort(employees, new EmployeeAgeComparator());
    System.out.println("根据年龄进行排序: " + employees);

    // 根据工资进行排序
    Collections.sort(employees, new EmployeeSalaryComparator());
    System.out.println("根据工资进行排序: " + employees);
  }
}

2,ThreadPoolExecutor 类

(1)java.util.concurrent.ThreadPoolExecutorJDK 中用于管理线程池的类,它是 Executor 接口的一个实现。它也使用了策略模式来处理不同的任务执行策略。

(2)ThreadPoolExecutor 提供了4种不同的策略来处理工作队列中的任务,例如:
  • ThreadPoolExecutor.AbortPolicy(默认):如果所有线程都在工作,该策略会拒绝新任务。
  • ThreadPoolExecutor.DiscardPolicy:如果所有线程都在工作,该策略会丢弃新任务。
  • ThreadPoolExecutor.DiscardOldestPolicy:如果所有线程都在工作,该策略会丢弃队列中最旧的任务。
  • ThreadPoolExecutor.CallerRunsPolicy:如果所有线程都在工作,该策略会让调用者线程执行新任务。

(3)ThreadPoolExecutor 的默认的策略是 ThreadPoolExecutor.AbortPolicy,如果我们希望使用其他策略可以在构造函数中指定。通过使用不同的策略,我们可以在同一个程序中使用多种任务执行策略来控制任务的执行和拒绝。
ThreadPoolExecutor executor = new ThreadPoolExecutor(1, 2, 60, TimeUnit.SECONDS,
        new ArrayBlockingQueue<>(3), 
        new ThreadPoolExecutor.DiscardOldestPolicy());

(4)下面是一个使用 ThreadPoolExecutor 类和 DiscardOldestPolicy 策略的示例代码,其中线程池大小为 1,最大线程数为 2,队列大小为 3. 当任务数量超过线程池和队列大小时,新任务会替换队列中最老的任务被执行。
public class Test {
  public static void main(String[] args) {
    // 创建一个线程池,初始线程数1,最大线程数2,线程空闲60s后回收,工作队列大小为3,丢弃最旧任务策略
    ThreadPoolExecutor executor = new ThreadPoolExecutor(1, 2, 60, TimeUnit.SECONDS,
            new ArrayBlockingQueue<>(3), new ThreadPoolExecutor.DiscardOldestPolicy());
    // 提交10个任务
    for (int i = 0; i < 10; i++) {
      executor.execute(new Task(i));
    }
  }
}

class Task implements Runnable {
  private int taskId;
  public Task(int taskId) {
    this.taskId = taskId;
  }
  @Override
  public void run() {
    System.out.println(LocalDateTime.now() + " - 任务 " + taskId + " 正在运行。");
    try {
      Thread.sleep(5000);
    } catch (InterruptedException e) {
      throw new RuntimeException(e);
    }
    System.out.println(LocalDateTime.now() + " - 任务 " + taskId + " 结束。");
  }
}

附二:Spring 中的策略模式

1,事务管理

(1)Spring 中的事务管理是通过 PlatformTransactionManager 接口来实现的,它定义了事务管理的基本操作,如开始、提交、回滚事务。而不同的事务管理器可以通过实现 PlatformTransactionManager 接口来实现不同的事务管理策略。例如:
  • DataSourceTransactionManager:基于 JDBC 的事务管理器
  • HibernateTransactionManager:基于 Hibernate 的事务管理器
  • JpaTransactionManager:基于 JPA 的事务管理器

(2)通过使用不同的事务管理器, 开发人员可以很方便地使用不同的事务管理策略,而不用关心具体的实现细节。编程人员只需要在配置文件中根据具体需要使用的事务类型做配置,Spring底层就自动会使用具体的事务实现类进行事务操作,而对于程序员来说,完全不需要关心底层过程,只需要面向 PlatformTransactionManager 接口进行编程即可。
注意:在单数据源的情况下我们无需自己管理事务,Spring Boot 默认使用 DataSourceTransactionManager 来管理事务,我们所做的只要在程序的 service 层加上 @Transactional 注解即可使用。

2,Spring AOP

(1)在 Spring 中,AOP(面向切面编程 Aspect-Oriented Programming)是通过代理机制实现的,也就是说,Spring 会在运行时为目标对象创建一个代理对象,并将切面逻辑织入到代理对象中。如果加入容器的目标对象有实现接口,Spring AOP 则用动态代理;如果目标对象没有实现接口,则用 Cglib 代理。

(2)而 DefaultAopProxyFactory 内部会根据配置(以及目标对象是类还是接口)来选择使用 JDK 动态代理或 CGLIB 代理来创建 AOP 代理,这就是使用了策略模式来实现不同的 AOP 代理创建策略。这种使用策略模式来选择不同的代理创建方式的实现方式,可以让开发人员更加灵活地配置和使用 AOP 代理,并且可以根据项目需求来选择最合适的代理创建策略。
public class DefaultAopProxyFactory implements AopProxyFactory, Serializable {
  private static final long serialVersionUID = 7930414337282325166L;

  public DefaultAopProxyFactory() {
  }

  public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
    if (NativeDetector.inNativeImage() || !config.isOptimize() && !config.isProxyTargetClass()
            && !this.hasNoUserSuppliedProxyInterfaces(config)) {
      return new JdkDynamicAopProxy(config);
    } else {
      Class<?> targetClass = config.getTargetClass();
      if (targetClass == null) {
        throw new AopConfigException("TargetSource cannot determine target class: " +
                "Either an interface or a target is required for proxy creation.");
      } else {
        return (AopProxy)(!targetClass.isInterface() && !Proxy.isProxyClass(targetClass)
                && !ClassUtils.isLambdaClass(targetClass) ? 
                new ObjenesisCglibAopProxy(config) : new JdkDynamicAopProxy(config));
      }
    }
  }

  private boolean hasNoUserSuppliedProxyInterfaces(AdvisedSupport config) {
    Class<?>[] ifcs = config.getProxiedInterfaces();
    return ifcs.length == 0 || ifcs.length == 1 && SpringProxy.class.isAssignableFrom(ifcs[0]);
  }
}
评论

全部评论(0)

回到顶部