📑本篇内容:23种设计模式 这栏文章让你拿捏得死死的 学不会来打我——行为性模型——策略模式
📘 文章专栏:Java23种设计模式一栏拿捏
🎬最近更新:2022年1月2日 23种设计模式 这栏文章让你拿捏得死死的 学不会来打我——结构性模型(上)——适配器模式 桥接模式 组合模式
🙊个人简介:一只二本院校在读的大三程序猿,本着注重基础,打卡算法,分享技术作为个人的经验总结性的博文博主,虽然可能有时会犯懒,但是还是会坚持下去的,如果你很喜欢博文的话,建议看下面一行~(疯狂暗示QwQ)
🌇点赞 👍 收藏 ⭐留言 📝 一键三连 关爱程序猿,从你我做起
📘本文目录
🌟前言🌟
23种设计模式下的六大原则
无论何种设计模式,都是基于六大设计原则:
- 开放封闭原则:一个软件实体如类、
模块和函数应该对修改封闭,对扩展开放
。 - 单一职责原则:
一个类就干一件事
,一个类应该有有一个引起它修改的原因。 - 里氏替换原则:子类应该可以完全替换掉父类。换句话来说就是在使用继承时,
只扩展新的功能,而不要破坏父类原有的功能
。 - 依赖倒置原则:细节应该依赖于抽象,抽象
不应该依赖于细节
。把抽象层放在程序设计的高层,并保持稳定,程序的细节变化由底层的实现层
来完成。 - 接口分离原则:客户端不该依赖它不需要的接口。如果一个接口在实现时,部分方法由于冗余被客户端空实现,则应该将接口拆分,让
实现类只需要以来自己需要的接口方法
。 - 迪米特法则:最少知道原则,尽量降低类与类之间的耦合;
一个对象应该对其他对象有最少的了解
。
📝策略模式(Strategy)
行为型模型(Behavioral Patterns)——策略模式(Strategy)
概要
什么是策略模式?如何理解?
通俗理解:就好比我们需要做同一件事
,但是有多种解决方案
,我们就可以对这些方法分别进行封装
(我们称之为这些被封装的每个方法为策略),根据不同的场景需求
,我们可以选择不同的策略
——调用不同的方法来达成我们统一的目的。
策略模式的定义
策略模式(Strategy Pattern):定义了一系列算法
,并且将每个算法进行封装行程策略,而且每个策略之间可以相互替换
。策略模式
让每个算法独立于使用它的客户而独立进行改变
变化。
按照我们比较常用的排序算法来举例,排序算法有很多种,比如说:冒泡排序、快速排序、桶排序、选择排序、插入排序等等,每个排序算法不同,但是目的结果都是将数据进行排序,这就可以用到我们上述所说的策略模式来进行相关定义。
示例讲解策略模式
这里咱们就用不同的排序方法来制定不同的排序策略,使其统一都可以完成排序功能~
由浅入深,复习8大排序算法的同时来加深对策略模式来进行理解。
在没有设计模式之前我们通常都是设计接口,使得不同的类实现相应的接口完成自身的相关独特方法。
我们可以先构造一个SortInterface
接口:
/***
* @author: Alascanfu
* @date : Created in 2022/2/25 17:01
* @description: 排序接口 用来对不同的排序方法进行对应的实现功能
* @modified By: Alascanfu
**/
public interface SortInterface {
int[] sort (int[] arr);
}
这样一来,我们就获得了用于对不同方法实现我们的排序这一功能的一个接口,我们只需要创建对应的实体类实现接口即可。
这里小付采取复习8大排序的同时顺便再次加深对策略模式的理解~
☀️BubbleSort
冒泡排序
学过数据结构的同学应该都知道冒泡排序是属于内部排序
,同时也是交换排序
。
冒泡排序
的基本思想以及思路:
- 通过对待排序序列,从前往后,相邻两两元素依次进行比较,如果发现
倒序则进行元素交换
,使得元素较大的值逐渐交换后移
。
/***
* @author: Alascanfu
* @date : Created in 2022/2/25 17:03
* @description: 冒泡排序
* @modified By: Alascanfu
**/
public class BubbleSort implements SortInterface{
@Override
public int[] sort(int[] arr) {
for (int i = 0 ;i < arr.length ;i++){
for (int j = 0 ; j < arr.length - i - 1;j++){
// 相邻两两元素进行顺序比较 如果倒叙则交换
if (arr[j] > arr[j+1]){
int tmp = arr[j+1];
arr[j+1] = arr[j];
arr[j] = tmp;
}
}
}
return arr;
}
@Test
public void test(){
int[] arr = new int []{5,2,14,32,11,8,9,11,23};
arr = sort(arr);
for (int i : arr) {
System.out.print(i+"\t");
}
}
}
☀️InsertSort
插入排序
插入排序
的基本思想以及思路:
- `默认认为第一个元素是已经排序好的,从第二个元素开始逐一进行前置判断,如果当前数据所处插入位置为正序则无需额外进行前置元素后移,反之倒叙需要前置元素后移,将数据插入合适的位置。
/***
* @author: Alascanfu
* @date : Created in 2022/2/25 19:35
* @description: 插入排序
* @modified By: Alascanfu
**/
public class InsertSort implements SortInterface{
@Override
public int[] sort(int[] arr) {
// 需要从第2个元素(下标为1的元素)开始
for (int i = 1 ;i< arr.length;i++){
// 记录当前需要进行排序的元素
int currentNum = arr[i];
// 从后往前开始进行排序
int j = i-1;
// 如果当前的元素小于前面元素的值则进行后移
while (j >= 0 && currentNum < arr[j]){
arr[j+1] = arr[j];
// 指针前移
j--;
}
// 到达需要插入当前元素的位置进行元素插入
arr[j+1] = currentNum;
}
return arr;
}
@Test
public void test(){
int[] arr = new int []{5,2,14,32,11,8,9,11,23};
arr = sort(arr);
for (int i : arr) {
System.out.print(i+"\t");
}
}
}
☀️SelectionSort
选择排序
选择排序
的基本思想以及思路:
- 选择排序的主要思路是根据当前下标元素来进行元素比较进行排序,每次遍历
找到当前遍历数据中的最小元素的下标
,将其所处下标元素与当前元素进行交换即可
。
/***
* @author: Alascanfu
* @date : Created in 2022/2/25 19:27
* @description: 选择排序
* @modified By: Alascanfu
**/
public class SelectionSort implements SortInterface{
@Override
public int[] sort(int[] arr) {
for (int i = 0 ; i < arr.length;i++){
// 初始化 设置当前元素下标为最小元素下标
int minIndex = i;
// 开始遍历 找到最小元素的下标 利用minIndex这个滚动变量进行相应的记录
for (int j = i + 1;j < arr.length;j++){
if (arr[minIndex] > arr[j]) {
minIndex = j;
}
}
// 找到最小的元素与当前元素进行交换
int tmp = arr[minIndex];
arr[minIndex] = arr[i];
arr[i] = tmp;
}
return arr;
}
@Test
public void test(){
int[] arr = new int []{5,2,14,32,11,8,9,11,23};
arr = sort(arr);
for (int i : arr) {
System.out.print(i+"\t");
}
}
}
☀️ShellSort
希尔排序
希尔排序
的基本思想以及思路:
- 初始增量分组为
数组个数的一半
,对每个分组分别进行直接插入排序
。 - 然后缩小增量再次进行插入排序,以此类推直至缩小增量到1时,
只有当前数组则已经完成好了排序操作
。
/***
* @author: Alascanfu
* @date : Created in 2022/2/25 21:25
* @description: 希尔排序
* @modified By: Alascanfu
**/
public class ShellSort implements SortInterface{
@Override
public int[] sort(int[] arr) {
// 设置一个增量为当前数组长度的一半
int gap = arr.length / 2;
// 直至增量分组缩小增量到1之前都要进行排序
while (gap > 0 ){
// 对于两两分组元素进行插入排序
for (int i = gap ; i< arr.length;i++){
int gapNum = arr[i];
int preIndex = i - gap;
while (preIndex >= 0 && arr[preIndex] > gapNum){
arr[gap + preIndex] = arr[preIndex];
preIndex -= gap;
}
arr[gap + preIndex] = gapNum;
}
gap /= 2;
}
return arr;
}
@Test
public void test(){
int[] arr = new int []{5,2,14,32,11,8,9,11,23};
arr = sort(arr);
for (int i : arr) {
System.out.print(i+"\t");
}
}
}
☀️QuickSort
快速排序
快速排序
的基本思想以及思路:
- 我们可以从数组中
选取一个基数(pivot)
- 遍历数组将
比基数大的数据放在基数右侧
,比基数小的数据放在基数左侧
,遍历完成之后数组分为左右两组元素
。 - 将左右两组元素在分别看成数组,重复上述步骤,直到排序完成。
/***
* @author: Alascanfu
* @date : Created in 2022/2/26 16:34
* @description: 快速排序
* @modified By: Alascanfu
**/
public class QuickSort implements SortInterface{
@Override
public int[] sort(int[] arr) {
quickSort(arr,0,arr.length-1);
return arr;
}
public void quickSort(int[] arr, int start ,int end){
// 跳出条件
if (start > end){
return ;
}
// 找到中间值的坐标
int mid = partition(arr, start, end);
// 递归求解
quickSort(arr,start,mid-1);
quickSort(arr,mid+1,end);
}
public int partition(int[] arr,int start,int end ){
int pivot = arr[start];
int left = start + 1;
int right = end ;
while (left < right){
while (left < right && arr[left] <= pivot) {
left++;
}
while (left < right && arr[right] >= pivot) {
right--;
}
if (left < right){
swap(arr,left,right);
left++;
right--;
}
}
if (left == right && arr[right] > pivot){
right--;
}
swap(arr, start, right);
return right;
}
// 交换函数
public void swap(int[] arr ,int num1 ,int num2 ){
int tmp = arr[num1];
arr[num1] = arr[num2];
arr[num2] = tmp;
}
@Test
public void test(){
int[] arr = new int []{5,2,14,32,11,8,9,11,23};
arr = sort(arr);
for (int i : arr) {
System.out.print(i+"\t");
}
}
}
更简单理解的快排
K神的快排求解方式:K神图解快排
class Solution {
public void quickSort(int arr[],int left ,int right ){
if (left >= right)return ;
int i = left;
int j = right;
while (i < j){
while (i < j && arr[j] >= arr[left])j--;
while (i < j && arr[i] <= arr[left])i++;
swap(arr,i,j);
}
swap(arr,i,left);
quickSort(arr,left,i-1);
quickSort(arr,i+1,right);
}
public void swap(int arr[],int i,int j){
int tmp = arr[i];
arr[i] = arr[j];
arr[j] = tmp;
}
}
☀️MergeSort
归并排序
归并排序
的基本思想以及思路:
- 分而治之的思想
我们先将数组进行折半分组
,划分左区域和右区域两个区域
,重复上述操作一直进行划分,直至划分至为一个元素的数组之后,再两两进行排序合并,最后完成排序操作。
/***
* @author: Alascanfu
* @date : Created in 2022/2/26 15:21
* @description: 归并排序
* @modified By: Alascanfu
**/
public class MergeSort implements SortInterface{
@Override
public int[] sort(int[] arr) {
mergeSort(arr,0, arr.length-1);
return arr;
}
public void mergeSort(int[] arr,int left ,int right ){
// 递归终止条件
if (left == right)return ;
// 折半分组
int mid = left + (right - left)/2;
// 对左侧的分组再次进行折半分组递归划分
mergeSort(arr,left,mid);
// 对右侧的分组再次进行折半分组递归划分
mergeSort(arr,mid+1,right);
// 对划分后的分组进行排序合并
merge(arr,left,mid,right);
}
public void merge(int[] arr ,int left,int mid ,int right ){
// 记录结果
int tmp[] = new int [right - left + 1];
// 记录结果指针
int resultIdx = 0 ;
// 折半数组左数组指针起始位置
int cur1 = left;
// 折半数组右数组指针起始位置
int cur2 = mid + 1;
// 进行排序合并数组
while (cur1 <= mid && cur2 <= right){
tmp[resultIdx++] = arr[cur1] < arr[cur2] ? arr[cur1++] : arr[cur2++];
}
// 如果还有划分的区域剩下了数据 则单独再将剩下的数据填充到临时结果数组中
while (cur1 <= mid){
tmp[resultIdx++] = arr[cur1++];
}
while (cur2 <= right){
tmp[resultIdx++] = arr[cur2++];
}
// 填入结果
for (int i = 0 ;i< tmp.length;i++){
arr[left + i] = tmp[i];
}
}
@Test
public void test(){
int[] arr = new int []{5,2,14,32,11,8,9,11,23};
arr = sort(arr);
for (int i : arr) {
System.out.print(i+"\t");
}
}
}
☀️HeapSort
堆排序
堆排序
的基本思想以及思路:
☀️BucketSort
桶排序
桶排序
的基本思想以及思路:
复习完八大排序方法之后我们接着来理解策略模式。
环境封装类Sort
其作用:我们此时已经针对不同的排序算法进行了封装,按照策略模式构成的定义,我们只需要将每种算法作为一种策略模式进行封装,对于不同的客户需求调用不同的排序算法即可。
/***
* @author: Alascanfu
* @date : Created in 2022/3/1 15:49
* @description: 对根据环境选择不同的排序方式进行封装
* @modified By: Alascanfu
**/
public class Sort implements SortInterface{
private SortInterface sort;
public Sort(SortInterface sort) {
this.sort = sort;
}
@Override
public int[] sort(int[] arr) {
return sort(arr);
}
// 用户根据自身需求来定义自身所需的排序策略
public void setSort(SortInterface sort){
this.sort = sort;
}
}
在该类中我们根据用户选择调用setSort方法实现不同的排序策略。
当然如果用户没有选择我们也可以设置一个默认执行的策略进行执行。
当然这种策略模式只是简单地实现
,也是基本的策略模式
,对于初步理解更加实用
,而目前框架中使用的策略模式才是我们应该进行学习掌握深入的
,构建属于自己对应所需的策略模式
。
构建策略枚举类SortStrategy
我们可以根据当前的策略模式稍加修改使其完善,修正它引起的迪米特法则
的缺陷,因为每次当我们使用者需要调用不同的策略
我们都必须要事先知道其对应策略的实现类,为了做到最少知道情况
,所以我们可以做出如下优化:
我们先构建一个策略枚举类,然后再回到环境封装类中采用工厂模式进行对枚举类的设置即可:
/***
* @author: Alascanfu
* @date : Created in 2022/3/1 16:17
* @description: 排序策略
* @modified By: Alascanfu
**/
public enum SortStrategy {
/** 冒泡排序*/
BUBBLE_SORT,
/** 插入排序*/
INSERT_SORT,
/** 归并排序*/
MERGE_SORT,
/** 快速排序*/
QUICK_SORT,
/** 选择排序*/
SELECTION_SORT,
/** 希尔排序*/
SHELL_SORT,
/** 堆排序*/
HEAP_SORT,
/** 桶排序*/
BUCKET_SORT
}
采用工厂模式封装环境类
/***
* @author: Alascanfu
* @date : Created in 2022/3/1 15:49
* @description: 对根据环境选择不同的排序方式进行封装
* @modified By: Alascanfu
**/
public class Sort implements SortInterface{
private SortInterface sort;
public Sort(SortStrategy sortStrategy) {
setSort(sortStrategy);
}
@Override
public int[] sort(int[] arr) {
return sort.sort(arr);
}
public void setSort(SortStrategy sortStrategy){
switch (sortStrategy){
case BUBBLE_SORT:
sort = new BubbleSort();
break;
case INSERT_SORT:
sort = new InsertSort();
break;
case MERGE_SORT:
sort = new MergeSort();
break;
case QUICK_SORT:
sort = new QuickSort();
break;
case SELECTION_SORT:
sort = new SelectionSort();
break;
case SHELL_SORT:
sort = new ShellSort();
break;
case HEAP_SORT:
sort = new HeapSort();
break;
case BUCKET_SORT:
sort = new BucketSort();
break;
default:
throw new RuntimeException("There's no such strategy yet.")
}
}
}
通过上述操作,我们只需要对Sort类中设置SortStrategy的策略就可以了,用户无需知道不同策略的具体实现类,该工厂模式将职责全部转交到了Sort这个类上。我们只需要对Sort的策略设置之后就可以进行sort()方法调用了。
🌟总结🌟
通过策略模式与工厂模式的合理利用
,可以大幅降低用户端的压力,用户只需提出需求策略
,而无需知道其内部具体实现
,而工厂模式将所有的职责交付给了工厂类
,而策略模式将不同的策略方法进行封装
,这样一来优化就游刃有余了。说到底,策略模式帮我们解决了对于相同的问题功能不同的处理办法的封装
类,对不同的需求使用不同的策略即可
。