写在模式学习之前
什么是设计模式:在我们进行程序设计时,逐渐形成了一些典型问题和问题的解决方案,这就是软件模式;每一个模式描述了一个在我们程序设计中经常发生的问题,以及该问题的解决方案;当我们碰到模式所描述的问题,就可以直接用相应的解决方法去解决这个问题,这就是设计模式。
设计模式就是抽象出来的东西,它不是学出来的,是用出来的;或许你根本不知道任何模式,不考虑任何模式,却写着最优秀的代码,即使以“模式专家”的角度来看,都是最佳的设计,不得不说是“最佳的模式实践”,这是因为你积累了很多的实践经验,知道“在什么场合代码应该怎么写”,这本身就是设计模式。
有人说:“水平没到,学也白学,水平到了,无师自通”。诚然,模式背熟,依然可能写不出好代码,更别说设计出好框架;OOP理解及实践经验到达一定水平,同时也意味着总结了很多好的设计经验,但"无师自通",却也未必尽然,或者可以说,恰恰是在水平和经验的基础上,到了该系统的学习一下“模式”的时候了,学习一下专家总结的结果,印证一下自己的不足,对于提高水平还是很有帮助的。
本系列的设计模式学习笔记,实际是对于《Java与模式》这本书的学习记录。
策略模式的定义
策略模式,其用意是针对一组算法,将每一个算法封装到具有共同接口的独立的类中,从而使得它们可以相互替换。策略模式使得算法可以在不影响到客户端的情况下发生变化。
策略模式把行为和环境分割开来。环境类负责维持和查询行为类,各种算法则在具体策略类(ConcreteStrategy)中提供。由于算法和环境独立开来,算法的增减、修改都不会影响环境和客户端。
策略模式是对算法的包装,是把使用算法的责任和算法本身分割开,委派给不同的对象管理。策略模式通常把一个系列的算法包装到一系列的策略类里面,作为一个抽象策略类的子类。用一句话来说,就是:“准备一组算法,并将每一个算法封装起来,使得它们可以互换。”
策略模式的结构
结构图
所涉及的角色
(1)环境(Context) 角色:持有一个Strategy类的引用。
(2)抽象策略(Strategy)角色:抽象角色,通常由一个接口或抽象类实现,此角色给出所有的具体策略类所需的接口。
(3)具体策略(ConcreteStrategy)角色:包装了相关的算法和行为。
代码实现
class Context
{
private Strategy strategy;
//策略方法
public void contextInterface()
{
strategy.strategyInterface();
}
}
abstract class Strategy
{
//策略方法
public abstract void strategyInterface();
}
class ConcreteStrategy extends Strategy
{
//策略方法
public void strategyInterface()
{
//Write your algorithm code here
}
}
策略模式使用场景
(1)如果一个系统里面有很多类,它们之间的区别仅在于它们的行为,那么使用策略模式可以动态地让一个对象在许多行为中选择一种行为。
(2)一个系统需要动态地在几种算法中选择一种。
(3)一个系统的算法使用的数据不可以让客户端知道。策略模式可以避免让客户端涉及到不必要接触到的复杂的和只与算法相关的数据。
(4)如果一个对象有很多的行为,如果不用恰当的模式,这些行为就只好使用多重的条件选择语句来实现。此时,使用策略模式,把这些行为转移到相应的具体策略类里面,就可以避免使用难以维护的多重选条件选择语句,并提现面向对象设计的概念。
演示一个策略案例:排序策略系统
含4种排序算法:冒泡排序、选择排序、插入排序、快速排序。
结构图
代码实现
import java.util.Arrays;
/*
排序算法有很多,所以在特定情景中使用哪一种算法很重要。为了选择合适的算法,可以按照建议的顺序考虑以下标准:
(1)执行时间
(2)存储空间
(3)编程工作
对于数据量较小的情形,(1)(2)差别不大,主要考虑(3);而对于数据量大的,(1)为首要。
比较以下4种常见的排序算法:
冒泡排序,比较中庸,性能耗费中既有比较,也有有效的相邻交换这中交换操作;
选择排序,性能耗费基本在比较,交换操作少;
插入排序,性能耗费既有判断,也有很多无效的顺序交换这种交换操作,只适用于少量数据的,并且已经基本排好序的场景;
快速排序,递归,二叉分区
稳定排序,是指对于相同大小的元素,在排序过程中相对位置不会发生变化;不稳定排序是指会发生变化。
*/
class StrategyTest
{
private Strategy s;
public void setStrategy(Strategy s)
{
this.s = s;
}
public void sort(){
s.sort();
}
public static void main(String[] args)
{
int[] a = new int[]{1,3,77,99,89,3,99,300,6,7};
StrategyTest st = new StrategyTest();
Strategy s1 = new BubbleSort(a);
st.setStrategy(s1);
st.sort();
System.out.println("冒泡排序:" + Arrays.toString(a));
a = new int[]{1,3,77,99,89,3,99,300,6,7};
Strategy s2 = new SelectSort(a);
st.setStrategy(s2);
st.sort();
System.out.println("选择排序:" + Arrays.toString(a));
a = new int[]{1,3,77,99,89,3,99,300,6,7};
Strategy s3 = new InsertSort(a);
st.setStrategy(s3);
st.sort();
System.out.println("插入排序:" + Arrays.toString(a));
a = new int[]{1,3,77,99,89,3,99,300,6,7};
Strategy s4 = new QuickSort(a);
st.setStrategy(s4);
st.sort();
System.out.println("快速排序:" + Arrays.toString(a));
a = new int[]{1,3,77,99,89,3,99,300,6,7};
Strategy s5 = new MergeSort(a);
st.setStrategy(s5);
st.sort();
System.out.println("归并排序:" + Arrays.toString(a));
}
}
abstract class Strategy
{
protected int[] a;
public Strategy(int[] a)
{
this.a = a;
}
protected void change(int i,int j)
{
int temp = a[i];a[i] = a[j];a[j] = temp;
}
public abstract void sort();
}
/**
* 冒泡排序(稳定排序):时间复杂度:O(n^2),原理是:共n-1轮排序处理,第j轮进行n-j次比较和至多n-j次交换
*
*/
class BubbleSort extends Strategy
{
public BubbleSort(int[] a) {
super(a);
}
@Override
public void sort() {
int n = a.length;
for(int i = 0; i< n -1;i++)
{
for(int j=0;j<n-i-1;j++)
{
if(a[j+1] < a[j])
change(j+1,j); //比较交换相邻元素
}
}
}
}
/**
* 选择排序(不稳定排序):时间复杂度:O(n^2),原理是:共n-1轮排序处理,每轮找出最小的放在相应位置
*
*/
class SelectSort extends Strategy
{
public SelectSort(int[] a) {
super(a);
}
@Override
public void sort() {
int n = a.length;
for(int i=0;i<n-1;i++)
{
int k = i;
for(int j=i+1;j<n;j++)
{
if(a[j] < a[k])
{
k = j;
}
}
if(k != i)
{
change(k,i);
}
}
}
}
/**
* 插入排序(稳定排序):时间复杂度:O(n^2),原理是:对数组中的第i个元素,认为它前面的i-1个已经排序好,然后将它插入到前面的i-1个元素中。
*
*/
class InsertSort extends Strategy
{
public InsertSort(int[] a) {
super(a);
}
@Override
public void sort() {
int n = a.length;
for(int i=1;i<n;i++) //循环从第二个数组元素开始,因为第一个作为最初已排序部分
{
int temp = a[i]; //temp标记为未排序第一个元素
int j = i-1;
while(j>=0 && a[j]>temp) //将temp与已排序元素从小到大比较,寻找temp应插入的位置
{
a[j+1] = a[j];
j--;
}
a[j+1] = temp;
}
}
}
/**
* 快速排序(不稳定排序):时间复杂度,O(nlogn),最坏情况为O(n^2),是对冒泡排序的改进,适用于排序大列表
* 基本思想:通过一次排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另一部分的小,然后对这两部分继续快速排序,达到整个数据变成有序序列
*
*/
class QuickSort extends Strategy
{
public QuickSort(int[] a) {
super(a);
}
@Override
public void sort() {
sort(0,a.length-1);
}
private int partition(int low,int high)
{
int key = a[low]; //标杆数据,小于标杆数据的放到一侧,大于的放到另一侧
while(low<high)
{
//小于标杆数据的放到左侧
while(low<high && a[high]>=key)
{
high--;
}
a[low] = a[high];
//大于标杆数据的放到右侧
while(low<high && a[low]<=key)
{
low++;
}
a[high] = a[low];
}
a[low] = key; //标杆数据放到中间
return low; //返回经过计算后标杆数据所在位置
}
private void sort(int low,int high)
{
if(low < high) //递归的死循环,会导致StackOverFlow
{
int r = partition(low,high);
sort(low,r-1);
sort(r+1,high);
}
}
}
class MergeSort extends Strategy {
public MergeSort(int[] a) {
super(a);
}
@Override
public void sort() {
merge(a,0,a.length-1,new int[a.length]);
}
private void merge(int[] a,int first,int last,int[] temp) {
if(first>=last) return;
int mid = (first + last) / 2;
merge(a,first,mid,temp); //左边排序
merge(a,mid+1,last,temp); //右边排序
mergeArray(a,first,mid,last,temp);
}
//将两个有序数组a[first..mid]和a[mid+1..last]合并
private void mergeArray(int[] a,int first,int mid,int last,int[] temp) {
int i = first,j=mid+1;
int k = 0;
while(i<=mid && j<=last) {
if(a[i] <= a[j])
temp[k++] = a[i++];
else
temp[k++] = a[j++];
}
while(i <= mid)
temp[k++] = a[i++];
while(j <= last)
temp[k++] = a[j++];
for(i=0;i<k;i++) {
a[first + i] = temp[i];
}
}
}
排序类别 | 时间复杂度 | 稳定排序 |
冒泡排序 | O(n^2) | √ |
选择排序 | O(n^2) | × |
插入排序 | O(n^2) | √ |
快速排序 | 最理想 O(nlogn) 、最差时间O(n^2) | × |
堆排序 | O(nlogn) | × |
归并排序 | O(nlogn) | √ |
希尔排序 | 平均时间 O(nlogn) 最差时间O(n^s) 1<s<2 | × |