【趣味設計模式系列】之【策略模式】

1. 簡介

策略模式(strategy):定義一組演算法,將每個演算法都封裝起來,並且使它們之間可以互換。

2. 圖解

商城搞多種優惠活動,顧客只能參與其中一種優惠演算法。

3. 案例實現

類圖

  • FullDistcount滿200減20元;
  • FirstPurchaseDiscount首次購買減20元;
  • SecondPurchaseDiscount第二件打9折;
  • HolidayDiscount節日一律減5元.

程式碼實現如下,環境類

package com.wzj.strategy;

/**
 * @Author: wzj
 * @Date: 2020/5/5 21:25
 * @Desc: 優惠類:環境類
 */
public class Context {
    private int price;
    private Discount discount;

    public Context(int price, Discount discount) {
        this.price = price;
        this.discount = discount;
    }

    public int getPrice() {
        return this.discount.calculateBySourcePrice(this.price);
    }
}

折扣介面類

package com.wzj.strategy;

/**
 * @Author: wzj
 * @Date: 2020/5/5 20:56
 * @Desc: 折扣優惠介面
 */
public interface Discount {
    public int calculateBySourcePrice(int price);
}

滿減優惠

package com.wzj.strategy;

/**
 * @Author: wzj
 * @Date: 2020/5/5 20:57
 * @Desc: 優惠滿減20元
 */
public class FullDiscount implements Discount {
    @Override
    public int calculateBySourcePrice(int price) {
        if (price > 200){
            System.out.println("優惠滿減20元");
            price = price - 20;
        }
        return price;
    }
}

首次優惠類

package com.wzj.strategy;

/**
 * @Author: wzj
 * @Date: 2020/5/5 21:11
 * @Desc: 首次購買減20元
 */
public class FirstPurchaseDiscount implements Discount {
    @Override
    public int calculateBySourcePrice(int price) {
        if (price > 100){
            System.out.println("首次購買減20元");
            price = price - 20;
        }

        return price;
    }
}

第二件優惠類

package com.wzj.strategy;

/**
 * @Author: wzj
 * @Date: 2020/5/5 21:05
 * @Desc: 第二件打9折
 */
public class SecondPurchaseDiscount implements Discount {
    @Override
    public int calculateBySourcePrice(int price) {
        System.out.println("第二件打9折");
        Double balance =  price * 0.9;

        return balance.intValue();
    }
}

節假日優惠類

package com.wzj.strategy;

/**
 * @Author: wzj
 * @Date: 2020/5/5 21:09
 * @Desc: 節日一律減5元
 */
public class HolidayDiscount implements Discount {
    @Override
    public int calculateBySourcePrice(int price) {
        if (price > 20){
            System.out.println("節日一律減5元");
            price = price - 5;
        }

        return price;
    }
}

測試類

package com.wzj.strategy;

/**
 * @Author: wzj
 * @Date: 2020/5/5 21:35
 * @Desc: 測試類
 */
public class TestStrategy {
    public static void main(String[] args) {
        Discount discount = new FirstPurchaseDiscount();
        Context context = new Context(110, discount);
        int price = context.getPrice();
        System.out.println(price);
    }
}

結果

首次購買減20元
90

4. JDK中的策略模式-Comparable與Comparator介面

4.1 Comparable

Comparable,在jdk1.8中描述如下,實現該介面的對象的List和array,可以通過Collections.sort和Arrays.sort自動排序,該對象具備sorted map的key和sorted set的元素的特徵。
源碼解析

  public interface Comparable<T> {
        public int compareTo(T o);
    }

該介面實現一個抽象方法compareTo,定義兩個對象的比較方式,返回值大於0、等於0、小於0,分別表示當前對象與傳入對象的關係為大於、相等、小於。

4.2 Comparator

Comparator為比較器,它可以作為一個參數傳遞到Collections.sort和Arrays.sort方法來指定某個類對象的排序方式。同時它也能為sorted set和sorted map指定排序方式。
源碼解析

@FunctionalInterface
    public interface Comparator<T> {
        // 唯一的抽象方法,用於定義比較方式(即排序方式)
        // o1>o2,返回1;o1=o2,返回0;o1<o2,返回-1
        int compare(T o1, T o2);
        boolean equals(Object obj);
        // 1.8新增的默認方法:用於反序排列
        default Comparator<T> reversed() {
            return Collections.reverseOrder(this);
        }
        // 1.8新增的默認方法:用於構建一個次級比較器,當前比較器比較結果為0,則使用次級比較器比較
        default Comparator<T> thenComparing(Comparator<? super T> other) {
            Objects.requireNonNull(other);
            return (Comparator<T> & Serializable) (c1, c2) -> {
                int res = compare(c1, c2);
                return (res != 0) ? res : other.compare(c1, c2);
            };
        }
        // 1.8新增默認方法:指定次級比較器的
        // keyExtractor表示鍵提取器,定義提取方式
        // keyComparator表示鍵比較器,定義比較方式
        default <U> Comparator<T> thenComparing(
                Function<? super T, ? extends U> keyExtractor,
                Comparator<? super U> keyComparator)
        {
            return thenComparing(comparing(keyExtractor, keyComparator));
        }
        // 1.8新增默認方法:用於執行鍵的比較,採用的是由鍵對象內置的比較方式
        default <U extends Comparable<? super U>> Comparator<T> thenComparing(
                Function<? super T, ? extends U> keyExtractor)
        {
            return thenComparing(comparing(keyExtractor));
        }
        // 1.8新增默認方法:用於比較執行int類型的鍵的比較
        default Comparator<T> thenComparingInt(ToIntFunction<? super T> keyExtractor) {
            return thenComparing(comparingInt(keyExtractor));
        }
        // 1.8新增默認方法:用於比較執行long類型的鍵的比較
        default Comparator<T> thenComparingLong(ToLongFunction<? super T> keyExtractor) {
            return thenComparing(comparingLong(keyExtractor));
        }
        // 1.8新增默認方法:用於比較執行double類型的鍵的比較
        default Comparator<T> thenComparingDouble(ToDoubleFunction<? super T> keyExtractor) {
            return thenComparing(comparingDouble(keyExtractor));
        }
        // 1.8新增靜態方法:用於得到一個相反的排序的比較器,這裡針對的是內置的排序方式(即繼承Comparable)
        public static <T extends Comparable<? super T>> Comparator<T> reverseOrder() {
            return Collections.reverseOrder();
        }
        // 1.8新增靜態方法:用於得到一個實現了Comparable介面的類的比較方式的比較器
        // 簡言之就是將Comparable定義的比較方式使用Comparator實現
        @SuppressWarnings("unchecked")
        public static <T extends Comparable<? super T>> Comparator<T> naturalOrder() {
            return (Comparator<T>) Comparators.NaturalOrderComparator.INSTANCE;
        }
        // 1.8新增靜態方法:得到一個null親和的比較器,null小於非null,兩個null相等,如果全不是null,
        // 則使用指定的比較器比較,若未指定比較器,則非null全部相等返回0
        public static <T> Comparator<T> nullsFirst(Comparator<? super T> comparator) {
            return new Comparators.NullComparator<>(true, comparator);
        }
        // 1.8新增靜態方法:得到一個null親和的比較器,null大於非null,兩個null相等,如果全不是null,
        // 則使用指定的比較器比較,若未指定比較器,則非null全部相等返回0
        public static <T> Comparator<T> nullsLast(Comparator<? super T> comparator) {
            return new Comparators.NullComparator<>(false, comparator);
        }
        // 1.8新增靜態方法:使用指定的鍵比較器用於執行鍵的比較
        public static <T, U> Comparator<T> comparing(
                Function<? super T, ? extends U> keyExtractor,
                Comparator<? super U> keyComparator)
        {
            Objects.requireNonNull(keyExtractor);
            Objects.requireNonNull(keyComparator);
            return (Comparator<T> & Serializable)
                (c1, c2) -> keyComparator.compare(keyExtractor.apply(c1),
                                                  keyExtractor.apply(c2));
        }
        // 1.8新增靜態方法:執行鍵比較,採用內置比較方式,key的類必須實現Comparable
        public static <T, U extends Comparable<? super U>> Comparator<T> comparing(
                Function<? super T, ? extends U> keyExtractor)
        {
            Objects.requireNonNull(keyExtractor);
            return (Comparator<T> & Serializable)
                (c1, c2) -> keyExtractor.apply(c1).compareTo(keyExtractor.apply(c2));
        }
        // 1.8新增靜態方法:用於int類型鍵的比較
        public static <T> Comparator<T> comparingInt(ToIntFunction<? super T> keyExtractor) {
            Objects.requireNonNull(keyExtractor);
            return (Comparator<T> & Serializable)
                (c1, c2) -> Integer.compare(keyExtractor.applyAsInt(c1), keyExtractor.applyAsInt(c2));
        }
        // 1.8新增靜態方法:用於long類型鍵的比較
        public static <T> Comparator<T> comparingLong(ToLongFunction<? super T> keyExtractor) {
            Objects.requireNonNull(keyExtractor);
            return (Comparator<T> & Serializable)
                (c1, c2) -> Long.compare(keyExtractor.applyAsLong(c1), keyExtractor.applyAsLong(c2));
        }
        // 1.8新增靜態方法:用於double類型鍵的比較
        public static<T> Comparator<T> comparingDouble(ToDoubleFunction<? super T> keyExtractor) {
            Objects.requireNonNull(keyExtractor);
            return (Comparator<T> & Serializable)
                (c1, c2) -> Double.compare(keyExtractor.applyAsDouble(c1), keyExtractor.applyAsDouble(c2));
        }
    }

JDK1.8之前,Comparator中只要兩個方法,就是前兩個方法,後面的所有默認方法均為1.8新增的方法,採用的是1.8新增的功能:介面可添加默認方法。即便擁有如此多方法,該介面還是函數式介面,compare用於定義比較方式

4.3 兩者區別

  • Comparable為可排序的,實現該介面的類的對象自動擁有可排序功能。
  • Comparator為比較器,實現該介面可以定義一個針對某個類的排序方式。
  • Comparator與Comparable同時存在的情況下,前者優先順序高。

4.4 實例

首先定義個類,Student

package com.wzj.strategy;

/**
 * @Author: wzj
 * @Date: 2020/5/6 21:15
 * @Desc:
 */
public class Student implements Comparable<Student>{

    private int age;
    private String name;

    public Student(int age, String name) {
        this.age = age;
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }


    @Override
    public int compareTo(Student o) {
        return this.age - o.age;
    }

    @Override
    public String toString() {
        return "Student{" +
                "age=" + age +
                ", name='" + name + '\'' +
                '}';
    }
}

定義年齡比較器

package com.wzj.strategy;

import java.util.Comparator;

/**
 * @Author: wzj
 * @Date: 2020/5/6 21:19
 * @Desc: 年齡比較器
 */
public class AgeComparator implements Comparator<Student> {
    @Override
    public int compare(Student o1, Student o2) {
        return o1.getAge() - o2.getAge();
    }
}

定義姓名比較器

package com.wzj.strategy;

import java.util.Comparator;

/**
 * @Author: wzj
 * @Date: 2020/5/6 21:19
 * @Desc: 姓名比較器
 */
public class NameComparator implements Comparator<Student> {
    @Override
    public int compare(Student o1, Student o2) {
        return o1.getName().charAt(0) - o2.getName().charAt(0);
    }
}

測試類

package com.wzj.strategy;

import java.util.Arrays;

/**
 * @Author: wzj
 * @Date: 2020/5/6 21:24
 * @Desc:
 */
public class TestComparator {
    public static void main(String[] args) {
        Student s1 = new Student(18, "zhangsan");
        Student s2 = new Student(15, "lisi");
        Student s3 = new Student(10,"wangwu");
        Student[] students = {s1, s2, s3};
        System.out.print("數組排序前:");
        printArray(students);
        System.out.println();
        Arrays.sort(students);
        System.out.print("數組通過Comparable介面排序後:");
        printArray(students);
        System.out.println();
        Arrays.sort(students, new AgeComparator());
        System.out.print("數組通過年齡比較器AgeComparator排序後:");
        printArray(students);
        System.out.println();
        Arrays.sort(students, new NameComparator());
        System.out.print("數組通過姓名比較器NameComparator排序後:");
        printArray(students);

    }

    public static void printArray (Student[] students) {
        for (Student student : students) {
            System.out.print(student.toString() + ",");
        }
    }
}

測試結果

數組排序前:Student{age=18, name='zhangsan'},Student{age=15, name='lisi'},Student{age=10, name='wangwu'},
數組通過Comparable介面排序後:Student{age=10, name='wangwu'},Student{age=15, name='lisi'},Student{age=18, name='zhangsan'},
數組通過年齡比較器AgeComparator排序後:Student{age=10, name='wangwu'},Student{age=15, name='lisi'},Student{age=18, name='zhangsan'},
數組通過姓名比較器NameComparator排序後:Student{age=15, name='lisi'},Student{age=10, name='wangwu'},Student{age=18, name='zhangsan'},

5. Spring源碼中的策略模式

Spring Bean 實例化,是通過InstantiationStrategy介面實現的,根據創建對象情況的不同,提供了三種方法:無參構造方法、有參構造方法、工廠方法。如下

public interface InstantiationStrategy {

 /**
  * 默認構造方法
  */
 Object instantiate(RootBeanDefinition bd, @Nullable String beanName, BeanFactory owner)
   throws BeansException;

 /**
  * 指定構造方法
  */
 Object instantiate(RootBeanDefinition bd, @Nullable String beanName, BeanFactory owner,
   Constructor<?> ctor, @Nullable Object... args) throws BeansException;

 /**
  * 工廠方法
  */
 Object instantiate(RootBeanDefinition bd, @Nullable String beanName, BeanFactory owner,
   @Nullable Object factoryBean, Method factoryMethod, @Nullable Object... args)
   throws BeansException;

}

InstantiationStrategy 介面有兩個實現類:SimpleInstantiationStrategy 和 CglibSubclassingInstantiationStrategy。SimpleInstantiationStrategy 對以上三個方法都做了簡單的實現。
如果是工廠方法實例化,則直接使用反射創建對象,如下:

public Object instantiate(RootBeanDefinition bd, @Nullable String beanName, BeanFactory owner,
   @Nullable Object factoryBean, final Method factoryMethod, @Nullable Object... args) {

  try {
   if (System.getSecurityManager() != null) {
    AccessController.doPrivileged((PrivilegedAction<Object>) () -> {
     ReflectionUtils.makeAccessible(factoryMethod);
     return null;
    });
   }
   else {
    ReflectionUtils.makeAccessible(factoryMethod);
   }

   Method priorInvokedFactoryMethod = currentlyInvokedFactoryMethod.get();
   try {
    currentlyInvokedFactoryMethod.set(factoryMethod);
    Object result = factoryMethod.invoke(factoryBean, args);
    if (result == null) {
     result = new NullBean();
    }
    return result;
   }
   finally {
    if (priorInvokedFactoryMethod != null) {
     currentlyInvokedFactoryMethod.set(priorInvokedFactoryMethod);
    }
    else {
     currentlyInvokedFactoryMethod.remove();
    }
   }
  }
  // 省略 catch
 }

如果是構造方法實例化,則是先判斷是否有 MethodOverrides,如果沒有則是直接使用反射,如果有則就需要 CGLIB 實例化對象。如下:

public Object instantiate(RootBeanDefinition bd, @Nullable String beanName, BeanFactory owner) {
  // Don't override the class with CGLIB if no overrides.
  if (!bd.hasMethodOverrides()) {
   Constructor<?> constructorToUse;
   synchronized (bd.constructorArgumentLock) {
    constructorToUse = (Constructor<?>) bd.resolvedConstructorOrFactoryMethod;
    if (constructorToUse == null) {
     final Class<?> clazz = bd.getBeanClass();
     if (clazz.isInterface()) {
      throw new BeanInstantiationException(clazz, "Specified class is an interface");
     }
     try {
      if (System.getSecurityManager() != null) {
       constructorToUse = AccessController.doPrivileged(
         (PrivilegedExceptionAction<Constructor<?>>) clazz::getDeclaredConstructor);
      }
      else {
       constructorToUse =   clazz.getDeclaredConstructor();
      }
      bd.resolvedConstructorOrFactoryMethod = constructorToUse;
     }
     catch (Throwable ex) {
      throw new BeanInstantiationException(clazz, "No default constructor found", ex);
     }
    }
   }
   return BeanUtils.instantiateClass(constructorToUse);
  }
  else {
   // Must generate CGLIB subclass.
   return instantiateWithMethodInjection(bd, beanName, owner);
  }
 }

 public Object instantiate(RootBeanDefinition bd, @Nullable String beanName, BeanFactory owner,
   final Constructor<?> ctor, @Nullable Object... args) {

  if (!bd.hasMethodOverrides()) {
   if (System.getSecurityManager() != null) {
    // use own privileged to change accessibility (when security is on)
    AccessController.doPrivileged((PrivilegedAction<Object>) () -> {
     ReflectionUtils.makeAccessible(ctor);
     return null;
    });
   }
   return (args != null ? BeanUtils.instantiateClass(ctor, args) : BeanUtils.instantiateClass(ctor));
  }
  else {
   return instantiateWithMethodInjection(bd, beanName, owner, ctor, args);
  }
 }

SimpleInstantiationStrategy 對 instantiateWithMethodInjection() 的實現任務交給了子類 CglibSubclassingInstantiationStrategy。
類 CglibSubclassingInstantiationStrategy 為 Spring 實例化 bean 的默認實例化策略,其主要功能還是對父類功能進行補充:其父類將 CGLIB 的實例化策略委託其實現

//SimpleInstantiationStrategy
protected Object instantiateWithMethodInjection(RootBeanDefinition bd, @Nullable String beanName, BeanFactory owner) {
  throw new UnsupportedOperationException("Method Injection not supported in SimpleInstantiationStrategy");
}

//CglibSubclassingInstantiationStrategy
@Override
protected Object instantiateWithMethodInjection(RootBeanDefinition bd, @Nullable String beanName, BeanFactory owner) {
  return instantiateWithMethodInjection(bd, beanName, owner, null);
}

CglibSubclassingInstantiationStrategy 實例化 bean 策略是通過其內部類 CglibSubclassCreator 來實現的。

protected Object instantiateWithMethodInjection(RootBeanDefinition bd, @Nullable String beanName, BeanFactory owner,
   @Nullable Constructor<?> ctor, @Nullable Object... args) {
  return new CglibSubclassCreator(bd, owner).instantiate(ctor, args);
}

6. 總結

優點

  • 演算法可以自由切換
  • 避免使用多重條件判斷
  • 擴展性良好

缺點

  • 策略類數量增多
  • 所有的策略類都需要對外暴露