策略模式
从comparator与comparable谈起
先从最基本的讲起,定义一个Sorter类,sort方法是选择排序的实现,实现对int数组a排序:
public class Sorter {
public void sort(int[] a)
{
for (int i=0;i<a.length;i++)
{
int minPos = i;
for (int j = i; j < a.length; j++)
minPos = a[j] < a[minPos] ? j : minPos;
int t = a[i];
a[i] = a[minPos];
a[minPos] = t;
}
}
}
再写个主函数,实现调用sort对a数组排序:
public class Main {
public static void main(String[] args) {
int[] a = {4,3,5,1,6,9,2};
Sorter sorter = new Sorter();
sorter.sort(a);
System.out.println(Arrays.toString(a));
}
}
现在思考一个问题:如果要是对Double类型的数组进行排序该怎么做呢?首先想到的就是重载一下sort,让它可以接受Double类型的数组,那如果想对Dog类,Cat类,Car类进行排序呢?每一个都要重写一次吗?麻不麻烦的先不提,但就sort那段代码重复这么多次,就很不友好。此时我们可以写一个接口:
public interface Comparable {
//这里的参数暂时用Object
int CompareTo(Object o);
}
然后我们写一个Cat类,并实现Comparable接口,重写CompareTo方法,实现Cat按照weight排序:
public class Cat implements Comparable{
int weight,height;
public Cat(int weight, int height) {
this.weight = weight;
this.height = height;
}
@Override
public int CompareTo(Object o) {
Cat c = (Cat) o;
if (this.weight<c.weight)
return -1;
else if (this.weight>c.weight)
return 1;
return 0;
}
}
修改我们的sort函数,修改参数为Comparable接口类型,这样的目的是只要我们实现Comparable接口的类,都可以用这个函数进行排序:
public void sort(Comparable[] a)
{
for (int i=0;i<a.length;i++)
{
int minPos = i;
for (int j = i; j < a.length; j++)
//修改 a[j].CompareTo(a[minPos])
minPos = a[j].CompareTo(a[minPos])==-1 ? j : minPos;
Comparable t = a[i];
a[i] = a[minPos];
a[minPos] = t;
}
}
主函数:
public class Main {
public static void main(String[] args) {
Cat[] a = {new Cat(3,3),new Cat(4,4),new Cat(1,1)};
Sorter sorter = new Sorter();
sorter.sort(a);
System.out.println(Arrays.toString(a));
}
}
//输出:[Cat{weight=1, height=1}, Cat{weight=3, height=3}, Cat{weight=4, height=4}]
可以看到已经对Cat进行了排序,之后我们如果想要对Dog排序,对Person排序,只要实现Comparable接口就可以。
再进一步思考:如果我想按照Cat的height排序,怎么办?答案是需要修改Cat类中CompareTo方法,那如果以后我想按照其他的排序呢?是不是每次都要修改CompareTo方法?这样依旧很麻烦
下面正式引入策略模式,
此时我们可以声明一个比较器接口:
public interface Comparator {
//暂时使用Object
int Compare(Object o1,Object o2);
}
然后我们声明一个Cat比较器类实现Comparator接口:
public class CatComparator implements Comparator{
@Override
public int Compare(Object o1, Object o2) {
Cat c1 = (Cat) o1;
Cat c2 = (Cat) o2;
if (c1.height<c2.height)
return -1;
else if (c1.height>c2.height)
return 1;
return 0;
}
}
现在我们再次修改sort函数:
public void sort(Comparable[] a,Comparator comparator)
{
for (int i=0;i<a.length;i++)
{
int minPos = i;
for (int j = i; j < a.length; j++)
minPos = comparator.Compare(a[j],a[minPos])==-1 ? j : minPos;
Comparable t = a[i];
a[i] = a[minPos];
a[minPos] = t;
}
}
我们在主函数中声明一个比较器传入sort函数:
public static void main(String[] args) {
Cat[] a = {new Cat(3,3),new Cat(4,4),new Cat(1,1)};
Sorter sorter = new Sorter();
sorter.sort(a,new CatComparator());
System.out.println(Arrays.toString(a));
}
这样做的好处就是,程序更加灵活,具有了更强的可扩展性,当我们需要按照别的属性进行排序的时候,只需要新建一个类,而不是去修改代码,这样做符合了开闭原则。
代码中还有不尽人意的地方,那就是Object
,每次使用都需要强转类型,写起来很是麻烦,所以我们修改为泛型:
Comparator接口:
public interface Comparator<T> {
int Compare(T o1,T o2);
}
sort函数:
public class Sorter<T> {
public void sort(T[] a,Comparator<T> comparator)
{
for (int i=0;i<a.length;i++)
{
int minPos = i;
for (int j = i; j < a.length; j++)
minPos = comparator.Compare(a[j],a[minPos])==-1 ? j : minPos;
T t = a[i];
a[i] = a[minPos];
a[minPos] = t;
}
}
}
CatComparator:
public class CatComparator implements Comparator<Cat>{
@Override
public int Compare(Cat c1, Cat c2) {
if (c1.height<c2.height)
return -1;
else if (c1.height>c2.height)
return 1;
return 0;
}
}
main:
public static void main(String[] args) {
Cat[] a = {new Cat(3,3),new Cat(4,4),new Cat(1,1)};
Sorter<Cat> sorter = new Sorter<>();
sorter.sort(a,new CatComparator());
System.out.println(Arrays.toString(a));
}
因为Comparator是函数式接口,所以我们可以直接使用lambda表达式:
public static void main(String[] args) {
Cat[] a = {new Cat(3,3),new Cat(4,4),new Cat(1,1)};
Sorter<Cat> sorter = new Sorter<>();
sorter.sort(a,(o1,o2)->{
if (o1.height>o2.height) return -1;
else if (o1.height<o2.height) return 1;
return 0;
});
System.out.println(Arrays.toString(a));
}
当我们需要进行按照不同类不同策略进行排序的时候,只需要声明新的XXXComparator类就可以了,其他的源码都不用改动,增强了可扩展性,这就是策略模式。