设计模式-策略模式

策略模式是一种对象行为模式,通过封装一系列算法并允许它们互换,确保算法变化不影响使用。它能减少重复代码,提供算法的可重用性和可扩展性。策略模式适用于需要动态选择算法或希望隐藏算法实现细节的场景。模式结构包括抽象策略类、具体策略类和环境类。例如,可应用于排序策略,通过工厂模式降低客户端使用的复杂性。

策略模式

不改变原有结果的条件下,允许过程行为的不同~

策略模式介绍

定义

定义了一系列算法,并将每个算法封装起来,使它们可以相互替换,且算法的变化不会影响使用算法的客户。 策略模式属于对象行为模式,它通过对算法进行封装,把使用算法的责任和算法的实现分割开来,并委派给不同的对象对这些算法进行管理。

优点

  • 策略模式提供了一系列的可供重用的算法族,恰当使用继承可以把算法族的公共代码转移到父类里面,从而避免重复的代码。
  • 策略模式可以提供相同行为的不同实现,客户可以根据不同时间或空间要求选择不同的。
  • 策略模式提供了对开闭原则的完美支持,可以在不修改原代码的情况下,灵活增加新算法。
  • 策略模式把算法的使用放到环境类中,而算法的实现移到具体策略类中,实现了二者的分离。

应用场景

  • 一个系统需要动态地在几种算法中选择一种时,可将每个算法封装到策略类中。
  • 一个类定义了多种行为,并且这些行为在这个类的操作中以多个条件语句的形式出现,可将每个条件分支移入它们各自的策略类中以代替这些条件语句。
  • 系统中各算法彼此完全独立,且要求对客户隐藏具体算法的实现细节时。
  • 系统要求使用算法的客户不应该知道其操作的数据时,可使用策略模式来隐藏与算法相关的数据结构。
  • 多个类只区别在表现行为不同,可以使用策略模式,在运行时动态选择具体要执行的行为。

策略模式结构与实现

结构

  • 抽象策略(Strategy)类
    定义了一个公共接口,各种不同的算法以不同的方式实现这个接口,环境角色使用这个接口调用不同的算法,一般使用接口或抽象类实现。
  • 具体策略(Concrete Strategy)类
    实现了抽象策略定义的接口,提供具体的算法实现。
  • 环境(Context)类
    持有一个策略类的引用,最终给客户端调用。

策略模式

模板实现

package behaviour.strategy.template;

/**
 * 抽象策略类
 */
interface Strategy {
    /**
     * 策略方法
     */
    void strategyMethod();
}

/**
 * 具体策略类A
 */
class ConcreteStrategyA implements Strategy {
    @Override
    public void strategyMethod() {
        System.out.println("具体策略A的策略方法被访问!");
    }
}

/**
 * 具体策略类B
 */
class ConcreteStrategyB implements Strategy {
    @Override
    public void strategyMethod() {
        System.out.println("具体策略B的策略方法被访问!");
    }
}

/**
 * 环境类
 */
class Context {
    private Strategy strategy;

    public Strategy getStrategy() {
        return strategy;
    }

    public void setStrategy(Strategy strategy) {
        this.strategy = strategy;
    }

    public void strategyMethod() {
        strategy.strategyMethod();
    }
}


public class StrategyTemplateClient {
    public static void main(String[] args) {
        Context c = new Context();
        c.setStrategy(new ConcreteStrategyA());
        c.strategyMethod();
        System.out.println("-----------------");
        c.setStrategy(new ConcreteStrategyB());
        c.strategyMethod();
    }
}

运行结果

具体策略A的策略方法被访问!
-----------------
具体策略B的策略方法被访问!

示例 排序策略
策略工厂模式 -> 策略模式 + 工厂模式

客户端类

public class SortStrategyClient {
    public static void main(String[] args) {
        final SortStrategyFactory factory = new SortStrategyFactory();
        factory.putSortStrategy("插入排序", new InsertSort());
        factory.putSortStrategy("选择排序", new SelectSort());
        factory.putSortStrategy("冒泡排序", new BubbleSort());
        factory.putSortStrategy("希尔排序", new ShellSort());
        factory.putSortStrategy("归并排序", new MergeSort());
        factory.putSortStrategy("快速排序", new QuickSort());
        factory.putSortStrategy("堆排序", new HeapSort());
        factory.putSortStrategy("计数排序", new CountingSort());
        factory.putSortStrategy("桶排序", new BucketSort());
        factory.putSortStrategy("基数排序", new RadixSort());

        final Context context = new Context();
//        context.setArray(new int[]{12, 3.4, 46, -123, -345, 3, 2, 9, 0, -2, 1, -6, 44});
        context.initRandomArray(39999);

        context.setSortStrategy(factory.getSortStrategy("插入排序"));
        context.testSort();

        context.setSortStrategy(factory.getSortStrategy("冒泡排序"));
        context.testSort();

        context.setSortStrategy(factory.getSortStrategy("选择排序"));
        context.testSort();

        context.setSortStrategy(factory.getSortStrategy("希尔排序"));
        context.testSort();

        context.setSortStrategy(factory.getSortStrategy("归并排序"));
        context.testSort();

        context.setSortStrategy(factory.getSortStrategy("快速排序"));
        context.testSort();

        context.setSortStrategy(factory.getSortStrategy("堆排序"));
        context.testSort();

        context.setSortStrategy(factory.getSortStrategy("计数排序"));
        context.testSort();

        context.setSortStrategy(factory.getSortStrategy("桶排序"));
        context.testSort();

        context.setSortStrategy(factory.getSortStrategy("基数排序"));
        context.testSort();
    }
}

抽象策略类

/**
 * 抽象策略类
 */
interface SortStrategy {
    /**
     * 策略方法
     *
     * @param array 需要排序的数组
     */
    void sort(int[] array);

    /**
     * 辅助使用的默认方法,交换数组中两个数
     *
     * @param array 数组
     * @param pos1  下标1
     * @param pos2  下标2
     */
    default void swap(int[] array, int pos1, int pos2) {
        if (pos1 == pos2) {
            return;
        }
        int t = array[pos1];
        array[pos1] = array[pos2];
        array[pos2] = t;
    }
}

策略类享元工厂

/**
 * 策略享元工厂
 */
class SortStrategyFactory {
    private HashMap<String, SortStrategy> strategies = new HashMap<>();

    public SortStrategy getSortStrategy(String s) {
        return strategies.get(s);
    }

    public void putSortStrategy(String s, SortStrategy strategy) {
        strategies.put(s, strategy);
    }
}

环境类


/**
 * 环境类
 */
class Context {
    private SortStrategy sortStrategy;

    private int[] array;

    public SortStrategy getSortStrategy() {
        return sortStrategy;
    }

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

    public void initRandomArray(int size) {
        array = new int[size];
        for (int i = 0; i < size; i++) {
            array[i] = (int) (Math.random() * 10_0000 * (Math.random() > 0.5 ? 1 : -1));
        }
    }

    public int[] getArray() {
        return array;
    }

    public void setArray(int[] array) {
        this.array = array;
    }

    public void sort() {
        if (array.length < 2) {
            return;
        }
        sortStrategy.sort(array);
    }

    public void testSort() {
        int[] res = Arrays.copyOf(array, array.length);
        final long start = System.currentTimeMillis();
        if (array.length >= 2) {
            sortStrategy.sort(res);
            ;
        }
        String s = Arrays.toString(res);
        if (s.length() > 100) {
            s = s.substring(0, 100) + " -> and so on ...";
        }
        System.out.println(s);
        final long end = System.currentTimeMillis();
        System.out.println("\t耗时:" + (end - start));
    }
}

具体策略类 -> 有点多,写了十种排序算法


/**
 * 具体策略类:插入排序
 * 通过构建有序序列,对于未排序数据,
 * 在已排序序列中从后向前扫描,找到相应位置并插入。
 */
class InsertSort implements SortStrategy {
    @Override
    public void sort(int[] array) {
        System.out.println("插入排序");
        for (int i = 0; i < array.length; i++) {
            int t = array[i];
            int pos = i - 1;
            while (pos >= 0 && array[pos] > t) {
                // 不是插入的位置,将该值右移
                array[pos + 1] = array[pos];
                pos--;
            }
            // 找到了插入的位置 插入到该位置后面
            array[pos + 1] = t;
        }
    }
}

/**
 * 具体策略类:冒泡排序
 * 重复地走访过要排序的数列,一次比较两个元素,如果它们的顺序错误就把它们交换过来
 */
class BubbleSort implements SortStrategy {
    @Override
    public void sort(int[] array) {
        System.out.println("冒泡排序");
        for (int r = array.length - 1; r > 0; r--) {
            for (int bubblePos = 0; bubblePos < r; bubblePos++) {
                if (array[bubblePos] > array[bubblePos + 1]) {
                    swap(array, bubblePos, bubblePos + 1);
                }
            }
        }
    }
}

/**
 * 具体策略类:选择排序
 * 首先在未排序序列中找到最小元素,
 * 存放到排序序列的起始位置,然后,
 * 再从剩余未排序元素中继续寻找最小元素,
 * 然后放到已排序序列的末尾。以此类推,
 * 直到所有元素均排序完毕。
 */
class SelectSort implements SortStrategy {
    @Override
    public void sort(int[] array) {
        System.out.println("选择排序");
        for (int i = 0; i < array.length; i++) {
            int minPos = i;
            for (int j = i + 1; j < array.length; j++) {
                if (array[j] < array[minPos]) {
                    minPos = j;
                }
            }
            swap(array, i, minPos);
        }
    }
}

/**
 * 具体策略类:希尔排序
 * 先将整个待排序的记录序列分割成为若干子序列分别进行直接插入排序,具体算法描述:
 * 选择一个增量序列t1,t2,…,tk,其中ti>tj,tk=1;
 * 按增量序列个数k,对序列进行k 趟排序;
 * 每趟排序,根据对应的增量ti,
 * 将待排序列分割成若干长度为m 的子序列,
 * 分别对各子表进行直接插入排序。
 * 仅增量因子为1 时,整个序列作为一个表来处理,表长度即为整个序列的长度。
 */
class ShellSort implements SortStrategy {
    @Override
    public void sort(int[] array) {
        System.out.println("希尔排序");
        for (int gap = array.length / 2; gap > 0; gap /= 2) {
            // gap 为 shell 间隙 进行有间隙的插入排序
            for (int start = 0; start < gap; start++) {
                // 选择分别 0~gap 作为起点,间隔为gap的插入排序
                for (int i = start; i < array.length; i += gap) {
                    int t = array[i];
                    int pos = i - gap;
                    while (pos >= 0 && array[pos] > t) {
                        // 不是插入的位置,将该值右移
                        array[pos + gap] = array[pos];
                        pos -= gap;
                    }
                    // 找到了插入的位置 插入到该位置后面
                    array[pos + gap] = t;
                }
            }
        }
    }
}

/**
 * 具体策略类:归并排序
 * 将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序
 * 把长度为n的输入序列分成两个长度为n/2的子序列;
 * 对这两个子序列分别采用归并排序;
 * 将两个排序好的子序列合并成一个最终的排序序列。
 */
class MergeSort implements SortStrategy {
    private int[] merge(int[] leftArray, int[] rightArray) {
        int ll = 0, rl = 0, lr = leftArray.length, rr = rightArray.length;
        int i = 0;
        int[] mergeArray = new int[lr + rr];
        while (ll < lr && rl < rr) {
            mergeArray[i++] = leftArray[ll] < rightArray[rl] ? leftArray[ll++] : rightArray[rl++];
        }
        while (ll < lr) {
            mergeArray[i++] = leftArray[ll++];
        }
        while (rl < rr) {
            mergeArray[i++] = rightArray[rl++];
        }
        return mergeArray;
    }

    private int[] mergeSort(int[] array) {
        // 边界 只剩下一个数
        if (array.length < 2) {
            return array;
        }
        // 切分
        int m = (array.length) >> 1;
        int[] leftArray = Arrays.copyOfRange(array, 0, m);
        int[] rightArray = Arrays.copyOfRange(array, m, array.length);
        return merge(mergeSort(leftArray), mergeSort(rightArray));
    }

    @Override
    public void sort(int[] array) {
        System.out.println("归并排序");
        int[] res = mergeSort(array);
        System.arraycopy(res, 0, array, 0, array.length);
    }
}

/**
 * 具体策略类:快排 log(n)
 * 每次选一个基点进行分治(小的放基点左边,大的放基点右边)
 */
class QuickSort implements SortStrategy {
    private void quickSort(int[] arr, int l, int r) {
        if (l < r) {
            int partitionIndex = partition(arr, l, r);
            quickSort(arr, l, partitionIndex);
            quickSort(arr, partitionIndex + 1, r);
        }
    }

    // 分区 并返回基点+1
    private int partition(int[] arr, int l, int r) {
        // 生成原始基点
        int pivot = l, index = pivot + 1;
        for (int i = index; i < r; i++) {
            if (arr[i] < arr[pivot]) {
                swap(arr, i, index);
                index++;
            }
        }
        // 原基点值 与 最后一个小于基点值的值交换 成功分区
        swap(arr, pivot, index - 1);
        // 此时index - 1就是基点的下标!!直接返回即可
        pivot = index - 1;
        return pivot;
    }

    @Override
    public void sort(int[] array) {
        System.out.println("快速排序");
        quickSort(array, 0, array.length);
    }
}

/**
 * 具体策略类:堆排序
 * -子结点的键值或索引总是大于它的父节点。
 * 1.构建大顶堆
 * 2.每次取最右下角节点(堆数组最后一个节点)与堆顶交换,堆数组长度-1
 */
class HeapSort implements SortStrategy {
    private final int ROOT = 0;

    /**
     * 构建小根堆
     * -从距离叶子节点最近的上一层节点开始,
     * -向上层节点依次调用heapfy进行
     *
     * @param arr 需要被构建的堆数组
     */
    private void buildMinHeap(int[] arr) {
        int notLeafNodeIndex = arr.length >> 1;
        while (notLeafNodeIndex >= 0) {
            tidyMinHeap(arr, notLeafNodeIndex--);
        }
    }

    /**
     * 堆调整(从arr堆的i节点开始向下调整)
     * -堆数组从0开始,左子节点2*i+1,右子节点2*i+2
     *
     * @param arr 堆数组
     * @param i   要调整的堆节点下标
     */
    private void tidyMinHeap(int[] arr, int i) {
        tidyMinHeap(arr, i, arr.length);
    }

    private void tidyMinHeap(int[] arr, int i, int len) {
        int l = (i << 1) | 1;
        int r = (i << 1) + 2;
        int largestIndex = i;
        if (l < len && arr[l] > arr[largestIndex]) {
            largestIndex = l;
        }
        if (r < len && arr[r] > arr[largestIndex]) {
            largestIndex = r;
        }
        if (largestIndex != i) {
            swap(arr, i, largestIndex);
            tidyMinHeap(arr, largestIndex, len);
        }
    }

    @Override
    public void sort(int[] array) {
        System.out.println("堆排序");
        buildMinHeap(array);
        int len = array.length;
        while (len > 0) {
            // 堆长度减一,获取被减去的节点,与对顶节点交换!然后对堆进行调整
            len--;
            swap(array, len, ROOT);
            tidyMinHeap(array, ROOT, len);
        }
    }
}

/**
 * 具体策略类:计数排序
 * -其核心在于将输入的数据值转化为键存储在额外开辟的数组空间中
 * -要求输入的数据必须是有确定范围的整数
 * <p>
 * 找出待排序的数组中最大和最小的元素;
 * 统计数组中每个值为i的元素出现的次数,存入数组C的第i项;
 * 对所有的计数累加(从C中的第一个元素开始,每一项和前一项相加);
 * 反向填充目标数组:将每个元素i放在新数组的第C(i)项,每放一个元素就将C(i)减去1。
 */
class CountingSort implements SortStrategy {
    /**
     * 使用数组来存计数 这个对数据范围差值有要求!不能跨度过大!
     *
     * @param array 需要排序的数组
     */
    @Override
    public void sort(int[] array) {
        System.out.println("计数排序");
        // 注意不要传递范围过大的数 (我之前设置的输入数据范围在0~10_0000)
        // 遍历一次获取最小值与最大值 -> o(n)
        int min = array[0], max = array[0];
        for (int v : array) {
            min = Math.min(min, v);
            max = Math.max(max, v);
        }
        final int[] countArr = new int[max - min + 1];
        for (int v : array) {
            countArr[v - min]++;
        }
        int index = 0;
        for (int i = 0; i < countArr.length; i++) {
            final int v = i + min;
            while (countArr[i] >= 1) {
                array[index++] = v;
                countArr[i]--;
            }
        }
    }
}



/**
 * 具体策略类:桶排序
 * -假设输入数据服从均匀分布,将数据分到有限数量的桶里,每个桶再分别排序
 *  (数组+链表)
 * 设置一个定量的数组当作空桶;
 * 遍历输入数据,并且把数据一个一个放到对应的桶里去;
 * 对每个不是空的桶进行排序;
 * 从不是空的桶里把排好序的数据拼接起来。
 */
class BucketSort implements SortStrategy {
    @Override
    public void sort(int[] array) {
        System.out.println("桶排序");
        // 设置桶的容量为 待排序数组的长度的 3/10 次方10000
        int bucketSize = (int) Math.pow(array.length, 0.3);

        int min = array[0], max = array[0];
        for (int v : array) {
            min = Math.min(min, v);
            max = Math.max(max, v);
        }

        int bucketCount = (max - min) / bucketSize + 1;
        List<Integer>[] buckets = new List[bucketCount];
        for (int i = 0; i < buckets.length; i++) {
            buckets[i] = new ArrayList<>();
        }

        // 利用映射函数将数据分配到各个桶中
        for (int i = 0; i < array.length; i++) {
            buckets[(array[i] - min) / bucketSize].add(array[i]);
        }
        int index = 0;
        for (int i = 0; i < buckets.length; i++) {
            // 对每个桶进行排序,这里调用系统函数(偷个懒)
            buckets[i].sort(Comparator.comparingInt(a -> a));
            for (int j = 0; j < buckets[i].size(); j++) {
                array[index++] = buckets[i].get(j);
            }
        }
    }
}

/**
 * 具体策略类:基数排序
 * -基数排序是按照低位先排序,然后收集;
 * -再按照高位排序,然后再收集;依次类推,直到最高位。
 *
 * -有时候有些属性是有优先级顺序的,先按低优先级排序,
 * -再按高优先级排序。最后的次序就是高优先级高的在前,
 * -高优先级相同的低优先级高的在前。
 *
 * 取得数组中的最大数,并取得位数;
 * arr为原始数组,从最低位开始取每个位组成radix数组;
 * 对radix进行计数排序(利用计数排序适用于小范围数的特点);
 */
class RadixSort implements SortStrategy {
    /**
     * 正数基数排序
     * @param array 正数数组
     */
    private void positiveRadixSort(int[] array) {
        if (array.length < 2) {
            return;
        }
        //得到数组中最大的数的位数
        int maxNum = array[0];
        for (int i = 1; i < array.length; i++) {
            if (array[i] > maxNum) {
                maxNum = array[i];
            }
        }
        //得到最大数是几位数
        int maxLength = (maxNum + "").length();

        //定义一个二维数组,表示10个桶, 每个桶就是一个一维数组
        int[][] bucket = new int[10][array.length];
        //每个桶存入了几个数字
        int[] everyBucketNum = new int[10];

        // n* = 10 的原因是
        //123取出个位数字是 123 % 10,即 123 / 1 %10
        //123 取出十位数字是123 / 10 % 10;
        //123 去除百位数字是123 /100 % 10
        //以此类推
        for (int i = 0, n = 1; i < maxLength; i++, n *= 10) {
            for (int j = 0; j < array.length; j++) {
                //取出每个元素的对应位的值
                int digit = array[j] / n % 10;
                //放入到对应的桶中
                bucket[digit][everyBucketNum[digit]] = array[j];
                everyBucketNum[digit]++;
            }
            //按照这个桶的顺序(一维数组的下标依次取出数据,放入原来数组)
            int index = 0;
            //遍历每一桶,并将桶中是数据,放入到原数组
            for (int k = 0; k < everyBucketNum.length; k++) {
                if (everyBucketNum[k] != 0) {
                    for (int l = 0; l < everyBucketNum[k]; l++) {
                        array[index++] = bucket[k][l];
                    }
                }
                //放回原数组后,需要将每个 everyBucketNum[k] = 0
                everyBucketNum[k] = 0;

            }
        }
    }

    @Override
    public void sort(int[] array) {
        System.out.println("基数排序排序");
        // 划分正数与负数
        int[] positiveArr = new int[array.length];
        int[] negativeArr = new int[array.length];
        int positiveIndex = 0, negativeIndex = 0;
        for (int v : array) {
            if (v >= 0) {
                positiveArr[positiveIndex++] = v;
            }else {
                negativeArr[negativeIndex++] = -v;
            }
        }
        positiveArr = Arrays.copyOf(positiveArr, positiveIndex);
        negativeArr = Arrays.copyOf(negativeArr, negativeIndex);
        positiveRadixSort(positiveArr);
        positiveRadixSort(negativeArr);
        negativeIndex--;
        positiveIndex = 0;
        int index = 0;
        while(negativeIndex >=0) {
            array[index++] = -negativeArr[negativeIndex--];
        }
        while (positiveIndex < positiveArr.length) {
            array[index++] = positiveArr[positiveIndex++];
        }
    }
}

运行结果

插入排序
[-99999, -99995, -99990, -99990, -99986, -99983, -99978, -99975, -99971, -99963, -99942, -99932, -99 -> and so on ...
	耗时:274
冒泡排序
[-99999, -99995, -99990, -99990, -99986, -99983, -99978, -99975, -99971, -99963, -99942, -99932, -99 -> and so on ...
	耗时:3589
选择排序
[-99999, -99995, -99990, -99990, -99986, -99983, -99978, -99975, -99971, -99963, -99942, -99932, -99 -> and so on ...
	耗时:728
希尔排序
[-99999, -99995, -99990, -99990, -99986, -99983, -99978, -99975, -99971, -99963, -99942, -99932, -99 -> and so on ...
	耗时:16
归并排序
[-99999, -99995, -99990, -99990, -99986, -99983, -99978, -99975, -99971, -99963, -99942, -99932, -99 -> and so on ...
	耗时:17
快速排序
[-99999, -99995, -99990, -99990, -99986, -99983, -99978, -99975, -99971, -99963, -99942, -99932, -99 -> and so on ...
	耗时:10
堆排序
[-99999, -99995, -99990, -99990, -99986, -99983, -99978, -99975, -99971, -99963, -99942, -99932, -99 -> and so on ...
	耗时:14
计数排序
[-99999, -99995, -99990, -99990, -99986, -99983, -99978, -99975, -99971, -99963, -99942, -99932, -99 -> and so on ...
	耗时:9
桶排序
[-99999, -99995, -99990, -99990, -99986, -99983, -99978, -99975, -99971, -99963, -99942, -99932, -99 -> and so on ...
	耗时:75
基数排序排序
[-99999, -99995, -99990, -99990, -99986, -99983, -99978, -99975, -99971, -99963, -99942, -99932, -99 -> and so on ...
	耗时:17
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值