多态的最重要特性就是支持可扩展性。比如,排序算法,多种不同数据类型的值想要排序,通过同一个方法名的方法来排序,可以通过方法重载实现。
问题:如果要对某些对象进行排序比较,而不是基本数据类型,怎么样排序?
Cat类
package Strategy;
public class Cat {
private int age;
private String name;
public Cat(int age, String name) {
super();
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 String toString() {
// 重写toString方法,System默认是输出toString方法
return name+"是"+age+"岁"+"\n";
}
}
排序工具类
package Strategy;
public class DataSorter {
/**
* 排序
* 排序口诀:冒择路兮快归堆(冒失的进入一条道路,就会很快的进入坟墓)
* 冒泡、选择、插入、希尔、快速、归并、堆排序
* @param a
*/
public static void sort(int[] a) {
for(int i=a.length;i>0;i--)
for(int j=0;j<i-1;j++){
if(a[j]>a[j+1])
svap(a,j,j+1);
}
}
//猫咪排序
public static void sort(Cat[] a) {
for(int i=a.length;i>0;i--)
for(int j=0;j<i-1;j++){
//通过猫咪的年龄比较猫咪的大小
if(a[j].getAge()>a[j+1].getAge())
svap(a,j,j+1);
}
}
//喵咪交换方法
private static void svap(Cat[] cats, int j, int i) {
Cat temp=cats[j];
cats[j]=cats[i];
cats[i]=temp;
}
private static void svap(int[] a, int j, int i) {
int temp=a[j];
a[j]=a[i];
a[i]=temp;
}
/**
* 打印
* @param a
*/
public static void p(int[] a) {
// TODO Auto-generated method stub
for(int i=0;i<a.length;i++){
System.out.print(a[i]);
}
System.out.print("\n");
}
//打印猫咪
public static void p(Cat[] cats) {
for(int i=0;i<cats.length;i++){
System.out.print(cats[i]);//默认输出的是Cat的toString()方法
}
}
}
测试
package Strategy;
public class Test {
public static void main(String[] args) {
int[] a={9,5,2,4,1};
DataSorter.sort(a);
DataSorter.p(a);
Cat[] cats={new Cat(5,"喵喵"),new Cat(7,"哈哈"),new Cat(3,"欢欢")};
DataSorter.sort(cats);
DataSorter.p(cats);
}
}
以上方法,DataSorter可以对基本类型int类型排序,也可以对Cat进行排序,可如果又要求对Dog类型排序,对于Student类型排序,甚至要求对于所有类型的对象都可以重写sort方法,都可以排序,那么DataSorter工具类得写成多大?得写多少个方法重用?
思维一:sort方法,不指定排序哪一种类型,全部用Object[],这样就可以对所有类型进行排序了,不过,这样写了之后,因为不知道具体类型,Cat类型的对象通过什么属性来比较大小呢?既然比较大小的方式是不能确定的,那么就可以通过Object的子类来确定通过什么属性来比较对象大小。
模拟Comparable接口
Comparable接口的作用:
1、定义类是否能比较大小
2、为了使写的算法能够重复使用(把通过什么属性比较交给对象自己)
jdk中的Comparable接口支持泛型
package Strategy;
public interface Comparable {
//返回值是用来得出比较结果的
//c是与之比较的对象
public int compareTo(Object c);
}
Cat实现Comparable接口,在Cat里面写通过什么属性来比较对象大小
package Strategy;
//Cat实现Comparable接口
public class Cat implements Comparable{
private int age;
private String name;
public Cat(int age, String name) {
super();
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 String toString() {
// 重写toString方法,System默认是输出toString方法
return name+"是"+age+"岁"+"\n";
}
//重写接口方法
@Override
public int compareTo(Object o) {
// instanceof:o是否是Cat的实例
if(o instanceof Cat){
Cat c=(Cat)o;
if(this.getAge()>c.getAge())
return 1;
else if(this.getAge()<c.getAge())
return -1;
else
return 0;
}
//如果o不是Cat的实例,应该就是比较不了的,需要抛出异常
return -100;//简单写法,实际上jdk中需要跑异常的
}
}
DataSorter工具类
package Strategy;
public class DataSorter {
/**
* 排序
* 排序口诀:冒择路兮快归堆(冒失的进入一条道路,就会很快的进入坟墓)
* 冒泡、选择、插入、希尔、快速、归并、堆排序
* @param a
*/
//猫咪排序,Object[] a也可以用Comparable[] a
public static void sort(Object[] a) {
for(int i=a.length;i>0;i--)
for(int j=0;j<i-1;j++){
//通过Comparable接口来比较对象大小,可以把对象强制转化为Comparable类型(多态),调用其CompareTo方法来比较两个对象。
//这是用来多态,只有实现了Comparable接口的对象来可以这样比较大小
Comparable o1=(Comparable)a[j];
Comparable o2=(Comparable)a[j+1];
if(o1.compareTo(o2)==1){
svap(a,j,j+1);
}
}
}
//喵咪交换方法
private static void svap(Object[] o, int j, int i) {
Object temp=o[j];
o[j]=o[i];
o[i]=temp;
}
/**
* 打印
* @param a
*/
//打印猫咪
public static void p(Object[] o) {
for(int i=0;i<o.length;i++){
System.out.print(o[i]);//默认输出的是Cat的toString()方法
}
}
}
DataSorter工具类一旦写好了,就可以对所有的对象进行排序,不需要再写什么方法重载了,而是把确定对象比较大小的依据写在对象内部了,不需要在工具类里面针对不同数据类型重载方法了,DataSorter的可扩展性就非常好了。(工具类算法可重用)
问题:以下代码写死了通过哪个属性比较
//重写接口方法
@Override
public int compareTo(Object o) {
// instanceof:o是否是Cat的实例
if(o instanceof Cat){
Cat c=(Cat)o;
if(this.getAge()>c.getAge())
return 1;
else if(this.getAge()<c.getAge())
return -1;
else
return 0;
}
//如果o不是Cat的实例,应该就是比较不了的,需要抛出异常
return -100;//简单写法,实际上jdk中需要跑异常的
}
现在是根据猫咪的年龄来比较大小,如果将来需要根据猫咪的重量、胡子等比较大小,而Cat实现Comparable接口的方法已经写定了。如果想要灵活的指定通过指定对象的什么属性(不确定性)比较大小,要增大程序的可扩展性,可以把实现比较的方法独立出来,通过不同属性的比较封装成一个比较器,原先对象实现Comparble接口的方法中不写具体通过某种方法比较,而是用某种与属性相关的特定的比较器来比较两个对象的大小。
Comparator接口:作为比较器
package Strategy;
//比较器
public interface Comparator {
int compare(Object o1,Object o2);
}
通过Age属性的年龄比较器CatAgeComparator
package Strategy;
//年龄比较器,实现Comparator接口
public class CatAgeComparator implements Comparator {
//猫咪的年龄比较器
@Override
public int compare(Object o1, Object o2) {
Cat c1=(Cat)o1;
Cat c2=(Cat)o2;
if(c1.getAge()>c2.getAge())
return 1;
else if(c1.getAge()<c2.getAge())
return -1;
else
return 0;
}
}
Cat类中实现Comparable接口的compare方法不再写具体通过何种属性比较,大小,而是交给属性比较器,且把属性比较器作为Cat的成员变量
package Strategy;
public class Cat implements Comparable{
private int age;
private String name;
//定义一个比较器作为成员变量,默认用年龄比较器,如果想要用别的比较器,则可以setComparator
private Comparator comparator=new CatAgeComparator();
public Cat(int age, String name) {
super();
this.age = age;
this.name = name;
}
......
get/set方法略
......
@Override
public String toString() {
// 重写toString方法,System默认是输出toString方法
return name+"是"+age+"岁"+"\n";
}
//重写接口方法
@Override
public int compareTo(Object o) {
//不通过Cat的某个属性来比较了,而是把比较大小交给猫咪的某个比较器
//return new CatComparator().compare(this, o);
//Cat设置了用什么比较器(成员变量),就用哪种比较器来比较
return comparator.compare(this, o);
}
}
Datasorter工具类用于排序算法,已经不需要再有什么改动了。
Comparable接口是用来定义类是可以通过属性来比较大小的
Comparator接口是作为比较器,即为具体的比较策略。
JDK中的Comparable接口、Comparator接口是支持泛型的
策略模式的泛型使用
使用java.lang.comparable、java.util.comparator接口带泛型的用法,就省去了类型转换了。
策略模式:进行比较大小的时候,定义一个策略比较器,由具体的策略来进行比较大小
当使用某个方法不确定的时候,可以用很强大的可扩展的策略模式,使用哪种方法具体由使用的策略决定。
《java与模式》中的总结:
1、策略模式把行为和环境分开来,环境类负责查询维护和查询行为类,各种算法则在具体策略类中提供(本例中,环境类就是DataSorter,具体策略类就是CatAgeComparator)。由于算法和环境独立开来,算法的增减或修改都不会影响环境和客户端(程序员)。(如果使用继承,会使得环境和行为强耦合在一起,不利于两者单个演化)
2、策略模式提供了管理相关的算法族的办法。策略类的等级结构定义了一个算法会行为族。当准备使用策略模式时,必须要找到包装的算法,看算法是否能从环境中分割开来,再考察这些算法是否会在以后发生变化。
3、策略模式就是对算法的包装,是把使用算法的责任和算法本身分开,为派给不同的对象管理
4、java的awt的LayoutMangage和swing的Border使用了策略模式。
5、策略模式不仅仅封装算法,还提供新算法插入到已有系统中,以及老算法从系统中“退休”的方便,策略模式并不决定在何时使用何种算法
6、策略模式只适用于客户端在几种算法中选择一种的情形,并不适用于客户端同时需要几种算法的情形。如果需要重复使用多种算法,不是单单使用策略模式就可以的,还需要进一步使用装饰模式。
7、一个系统中算法使用的数据不可以让客户端知道,策略模式可以避免让客户端涉及到不必要接触到的复杂的和只与算法有关的数据。
策略模式和适配器模式的区别:
适配器模式的用意是允许一个客户对象通过调用一个配备着完全不同的接口的对象来完成它原来所要做的功能。
策略模式的用意是使用统一 的接口为客户端提供不同的算法。
策略模式和MVC模式的关系
视图常常是一个可视构件,而控制器需要对用户界面的事件作出反应,如果当用户单击一个按键构建后,控制器需要首先使按键处于禁止状态,然后运行按键所代表的操作,再重新允许按键事件发生。这里按键所代表的操作可根据视窗上用户所做选择的改变而改变–策略模式。
策略模式和装饰模式
策略模式在保持接口不变的情况下,使具体算法可以改变
装饰模式的用意是在不改变接口的情况下,增强一个对象的功能
windows程序使用策略模式举例
例如屏幕保护程序,每一个屏幕保护程序都有不同的参数需要设定(显示属性),每一种程序提供不同的显示算法,在屏幕上放映出不同的效果。当用户选择一个屏幕保护程序之后,就需要设定屏幕的参数。
这是典型的策略模式的应用:
1、每一个屏幕保护程序都有一个自己的参数设定窗口。这个窗口便是具体策略角色,它代表不同的算法。
2、用户可以选择一个算法并设定其参数,这些独立的策略对象背后有一个共同的抽象接口约束他们。