面向实现编程的弊端
举一个简单的动物界的例子,我们需要写一段代码,包含以下要素:鸭子类,鸭子的若干子类,实现一些方法。
惯用的编程思路:
1、写一个鸭子类Duck,写几个方法,比如鸭子飞行的方法fly(),鸭子叫的方法quack()。
2、写鸭子的子类,比如绿头鸭MallardDuck,继承Duck类。
3、在子类中重写以上两个方法。
这种编程思路的弊端:
1、代码冗杂:
在代码量少且代码稳定不变更的情况下似乎不会有什么问题,在实际运行程序时,也是完全不会出错的。但是,假设我们把鸭子的数量增加,增加到一万种鸭子类,我们是不是要重写一万次方法呢?显然这是很费事费力的。另外,假设我们要调用一万次fly()方法,是不是要写一万次调用的程序呢?即:对象名.fly() 。这也会导致代码的冗杂。
2、变更性弱:
假设当你下定决心去写那一万种鸭子类,并且快要写完的时候,老板和你说,这个fly()方法不应该这样写,需要改一下,你是不是就要崩溃了,代码几乎是死的,没法变更!
简单分析:这种类型的代码属于硬代码,不仅写的时候代码量繁杂,而且写完后,几乎是没法动的,自然不能适应现在软件需要不断更新版本的要求,需要我们转变编程思路。
面向接口编程的引入
我们一个个地解决这些问题。
1、解决代码冗杂的问题:减少代码量的最好方式就是相同代码的复用,假设一万种鸭子中,有一部分鸭子的fly()方法(行为)是一致的,那么我们可以试着去减少它重复的书写。fly()既需要满足被多处复用的要求,又需要满足可以被特殊地实现的要求,于是我们可以把这种飞行的行为单独地拿出来,使它脱离鸭子类,写一个接口flyBehavior,让fly()方法成为它的成员方法,fly()方法在这里是一种抽象方法,实例化必须改写内容。然后,我们可以写若干子类继承flyBehavior接口,实现行为方法fly(),然后在需要用的时候实例化调用就可以了。似乎这还没有解决一万次调用方法的代码冗杂问题,一万个对象,一万个对象名.fly()肯定是不现实的,我们希望的duck.fly()。解决的方法是在创建对象的时候让子类对象向上转型为父类,通过父类的引用去调用被子类重写后的方法,那么这个问题也解决了。
2、解决变更性弱的问题:这个问题其实已经被上述方法解决,我们修改fly()方法的内容,只要在相应的行为类中修改就可以了,操作起来很方便。其他行为如quack()行为也可以作为接口。除此之外,我们还可以做一些别的事情,比如实现鸭子飞行行为的动态更改,只要在鸭子类中写一个set方法,修改flyBehavior的引用,使得flyBehavior调用不同的fly()方法,实现在程序运行中多态修改飞行参数以及飞行姿态。
小结:以上就体现了面向接口编程的特点:
1、反复被使用的行为被抽象为接口,利用接口的多实现使得行为多实现。
2、行为的反复重写变为了对已经实现的行为的多次调用。
3、父类对象调用子类方法体现了自动转型的思想。
4、实现了多态。
面向接口编程实践
1、Duck类:
public abstract class Duck {
FlyBehavior flyBehavior;
QuackBehavior quackBehavior;
/**
* 鸭子的外观
*/
public abstract void display();
/**
* 鸭子的身份
*/
public void isDuck()
{
System.out.println("我是鸭子");
}
/**
* 使用飞行的算法
*/
public void flyPerfrom()
{
flyBehavior.fly();
}
/**
* 使用叫的算法
*/
public void quackPerform()
{
quackBehavior.quack();
}
/**
* 设置飞行的算法
* @param flyBehavior
*/
public void setFlyBehavior(FlyBehavior flyBehavior)
{
this.flyBehavior=flyBehavior;
}
/**
* 设置叫的算法
* @param quackBehavior
*/
public void setQuackPerform(QuackBehavior quackBehavior)
{
this.quackBehavior=quackBehavior;
}
}
2、绿头鸭子类:
public class MallardDuck extends Duck{
/**
* 鸭子外观方法
* 重写的抽象方法
*/
@Override
public void display() {
System.out.println("一只绿头鸭");
}
/**
* 构造方法
* 实现动物行为的接口
*/
public MallardDuck()
{
flyBehavior=new FlyWithWing();
quackBehavior=new Quack();
}
}
3、飞行行为接口:
public interface FlyBehavior {
public abstract void fly();
}
4、鸭叫行为接口:
public interface QuackBehavior {
public abstract void quack();
}
5、两种飞行行为的实现类:
public class FlyWithWing implements FlyBehavior{
@Override
public void fly() {
System.out.println("用翅膀飞行");
}
}
public class FlyNoWing implements FlyBehavior{
@Override
public void fly() {
System.out.println("没有翅膀,飞不起来");
}
}
6、一种鸭叫行为的实现类:
public class Quack implements QuackBehavior{
@Override
public void quack() {
System.out.println("绿头鸭的叫声");
}
}
7、测试类:
public static void main(String[] args)
{
Duck duck = new MallardDuck();
duck.flyPerfrom();//执行飞行的算法
duck.quackPerform(); //执行鸭叫的算法
duck.display(); //外观展示方法
duck.isDuck(); //鸭子的类别判断方法
duck.setFlyBehavior(new FlyNoWing());//设飞行算法
duck.flyPerfrom(); //执行飞行算法
}
8、运行结果:
用翅膀飞行
绿头鸭的叫声
一只绿头鸭
我是鸭子
没有翅膀,飞不起来
9、分析:
我对三种不同的方法有不同的处理:对于行为方法,让其变为接口;对于鸭子的通用方法,比如上述鸭子身份的判断,在鸭子类中直接具体化,子类继承拿来直接用;对于每个鸭子的不同属性,比如外观display(),作为抽象方法,每个子类都需要实现。行为方法的接口化,实现了面向接口编程,另外,在鸭子类中,先申明了接口的对象,然后在perform方法写接口对象对抽象方法的调用,在实例化鸭子对象前,这些引用并不指向任何具体内容,实例化后,接口对象指向了具体被实现的抽象方法,便可以调用。案例中,我们实现了在程序运行中对鸭子飞行行为的修改,这种思想可以类比于我们在玩一个射击类游戏中切换武器,不同的武器就可以定义在一个算法族中,共同实现了武器接口。
策略模式的几项原则
以上我们学习的,其实就是设计模式中的策略模式,我们已经学会了使用设计模式,接下来,我给大家总结一下策略模式涉及到的几项OO原则吧:
1、找出应用中那些需要经常变化的部分,把它们独立出来“封装”,避免和那些不需要变化的代码混杂。
2、针对接口编程而不是针对实现编程。
3、多用组合,少用继承。
策略模式定义了算法族(我们把行为改称为算法,算法族即那些被封装的行为的统称),分别封装起来,让他们之间可以相互替换,此模式让算法的变化独立于使用算法的客户。
策略模式优缺点
策略模式的优点:
- 算法可以自由切换;
- 避免使用多重条件判断;
- 扩展性良好。
策略模式的缺点:
- 策略类会增多
- 所有策略类都需要对外暴露
策略模式的适用场景:
- 当一个系统中有许多类,它们之间的区别仅在于它们的行为,希望动态地让一个对象在许多行为中选择一种行为时;
- 当一个系统需要动态地在几种算法中选择一种时;
- 当一个对象有很多的行为,不想使用多重的条件选择语句来选择使用哪个行为时。
Java 对象排序中的应用
Comparator 外部比较器接口
我们如果需要控制某个类的次序,而该类本身不支持排序(即没有实现Comparable接口);那么可以建立一个该类的比较器来排序,这个比较器只需要实现Comparator接口即可。,通过实现Comparator类来新建一个比较器,然后通过该比较器来对类进行排序。Comparator 接口其实就是一种策略模式的实践
事例代码:
//抽象策略类 Comparator
public interface Comparator<T> {
int compare(T o1, T o2);
boolean equals(Object obj);
}
//具体策略类 SortComparator
public class SortComparator implements Comparator {
@Override
public int compare(Object o1, Object o2) {
Student student1 = (Student) o1;
Student student2 = (Student) o2;
return student1.getAge() - student2.getAge();
}
}
//策略模式上下文 Collections
public class Client {
public static void main(String[] args) {
Student stu[] = {
new Student("张三" ,23),
new Student("李四" ,26)
, new Student("王五" ,22)};
Arrays. sort(stu,new SortComparator());
System.out.println(Arrays.toString(stu));
List<Student> list = new ArrayList<>(3);
list.add( new Student("zhangsan" ,31));
list.add( new Student("lisi" ,30));
list.add( new Student("wangwu" ,35));
Collections. sort(list,new SortComparator());
System.out.println(list);
}
}
//数据流
countRunAndMakeAscending:355, TimSort (java.util)
sort:220, TimSort (java.util)
sort:1438, Arrays (java.util)
main:20, Client (designpattern.strategy.compare)
调用Collections.sort方法之后走的是Arrays.sort()方法,然后TimSort类中countRunAndMakeAscending方法中调用具体比较器的算法实现进行比较,完成排序。这是大家比较常用的对象排序工具类。