接口与多态

一个海洋乐园游戏,当中所有东西都会游泳。知道继承可以运用多态,定义fish类,类中有个swim()的行为:

package hello;

public abstract class Fish {
    protected String name;
    public Fish(String name) {
        this.name=name;
    }
    public String getName() {
        return name;
    }
    public abstract void swim();
}

由于实际上每种鱼游泳方式不同,所以将swim()定义为 abstract,因此Fish也是abstract。接着定义鲨鱼继承鱼

public class Shark extends Fish{

    public Shark(String name) {
        super(name);
    }

    @Override
    public void swim() {
        System.out.printf("鲨鱼%s 游泳%n",name);
    }
}

鲨鱼 Shark类继承了Fish,并操作swim()方法,也许你还定义了食人鱼等等。人也会游泳啊,于是就再定义Huma类继承Fish。Human继承Fish?不会觉得很奇怪吗?就目前为止,程序也可以执行,继承会有是一种(is-a)的关系,所以 Anemonefish是一种fish, Shark是一种Fish. Piranha是一种Fish,如果你让 Human继承Fish,那 Human是一种Fish?
程序上可以通过编译也可以执行,但逻辑上或设计上有不合理的地方,你可以继续硬掰下去,如果现在加个潜水艇呢?写个 submarine继承Fish吗? Submarine是一种Fish吗?继续这样的想法设计下去,你的程序架构会越来越不合理,越来越没有弹性。Java中只能继承一个父类,所以更强化了“是一种”关系的限制性。如果把海洋乐园变为海空乐园,有的东西会游泳,有的东西会飞,有的东西会游也会飞,如果用继承方式来解决,写个Fish让会游的东西继承,写个Bird让会飞的东西继承,那会游也会飞的怎么办?
“所有东西”都会“游泳”,代表了“游泳”这个“行为”可以被所有东西拥有,而不是“某种”东西专属。对于“定义行为”,在Java中可以使用
interface关键字定义 :

package hello;

public interface Swimmer {
    public abstract void swim();
}

以上程序代码定义了 Swimmer接口,接口可以用于定义行为但不定义操作,在这里 Swimmer中的swim()方法没有操作,直接标示为abstract,而且一定是 public。对象若想拥有 Swimmer定义的行为,就必须操作 Swimmer接口。例如,Fish拥有 Swimmer行为:

package hello;

public abstract class Fish implements Swimmer{
    protected String name;
    public Fish(String name) {
        this.name=name;
    }
    public String getName() {
        return name;
    }
    @Override
    public abstract void swim();
}

类要操作接口,必须使用 implements关键字。操作某接口时,对接口中定义的方法有两种处理方式,一是操作接口中定义的方法,二是再度将该方法标示为 abstract。在这个范例中,由于Fish并不知道每条鱼怎么游所以使用第二种处理方式。那么,如果 Human要能游泳:

package hello;

public abstract class Human implements Swimmer{
    protected String name;
    public Human(String name) {
        this.name=name;
    }
    public String getName() {
        return name;
    }
    @Override
    public void swim(){
        System.out.printf("人类%s 游泳%n",name);
    }
}

以Java的语意来说,继承会有“是一种”关系,操作接口则表示“拥有行为”,但不会有“是一种”的关系。 Human与Submarine操作了 Swimmer,所以都拥有 Swimmer定义的行为,但它们没有继承Fish,所以它们不是一种鱼,这样的架构比较合理也较有弹性,可以应付一定程度的需求变化。


有些书或文件会说, Human与 Submarine是一种 Swimmer。会有这种说法的作者,应该是有C++程序语言的背景,因为C++中可多重继承,也就是子类可以拥有两个以上的父类若其中一个父类用来定义抽象行为,该父类的作用就类似Java中的接口,因为也是用继承语意来操作,所以才会有“是一种”的说法。多重继承容易因为设计上考虑不周而引来不少麻烦,因而Java对多重继承做了限制,就类别的语意来说,Java中限制只能继承一个父类别,所以“是一种”的语意更为强烈。建议将“是一种”的语意保留给继承,对于接口操作则使用“拥有行为”的语意,这样就不会搞不清楚类继承与接口操作的差别,对于何时用继承,何时用接口也比较容易判断广义来说,Java的接口确实是支持多重继的一种方式,不过在JDK8出现前,Java的接口只能定义抽象方法,不能有任何方法实现,这也是Java对多重继承做限制以避免复杂度的表现,但也引来设计上的一些不便之处。为了支持 Lambda新特性的引入,从JDK8开始,Java的接口也放宽了一些限制,接口中也可以有条件地进行方法实现。


行为的多态
会使用接口定义行为之后,也要再来当编译程序,看看哪些是合法的多态语法。例如:

Swimmer swimmer=new Shark();
Swimmer swimmer=new Human();
Swimmer swimmer=new Submarine();

这三行程序代码都可以通过编译,判断方式是“右边是不是拥有左边的行为”,或者右边对象是不是操作了左边接口”。
shark拥有 Swimme r行为吗?有的,因为Fish操了 Swimmer接口,也就是Fish拥有Swimmer行为, Shark继承Fish,当然也拥有 wimmer行为,所以通过编译, Human与Submarine也都操作了Swimmer接口,所以通过编译。

 Swimer swimmer=new Shark();
 Shark shark=swimmer;

第一行要判断 Shark是否拥有 Swimmer行为,是的可通过编译,但第二行编译程序看到该行会想到,有 Swimmer行为的对象是不是 Shark呢?这可不一定,也许实际上是 Human实例。编译失败。
行为的对象不一定是 shark,,所以第二就上面的代码段而言,实际上 swimmer是参考至 shark实例。可以加上扮演(Cas语法 )

 Swimer swimmer=new Shark();
 Shark shark=(Shark)swimmer;

对第二行的语意而言,就是在告诉编译程序,对!你知道有 Swimmer行为的对象,不一定是 Shark,不过你就是要它扮演 Shark,,所以编译程序就别再啰唆了。可以通过编译,执行时期 swimmer确实也是参考 Shark实例,所以也没有错误。
下面的程序片段会在第二行编译失败

 Swimmer swimmer=new Shark();
 Fish fish = swimmer;

第二行 swimmer是 Swimmer类型,所以编译程序会问,操作 Swimmer接口的对象是不是继承Fish?不一定,也许是Submarine,所以编译失败了。如果加上扮演语法:

 Swimmer swimmer=new Shark();
 Fish fish = (Fish)swimmer;

第二行告诉编译程序,你知道有 Swimmer行为的对象,不一定继承Fish,不过你就是要它扮演Fish,所以编译程序就别再啰唆了。可以通过编译,执行时期 swimmer确实也是参考 Shark实例,它是一种Fish,所以也没有错误
下面这个例子就会抛出 Classcastexception错误:

Swimmer swimmer=new Human();
shark shark =(Shark)swimmer;

在第二行, swimmer实际上参考了human实例你要他扮演鲨鱼,所以执行时就出错了。知道以下的语法,哪些可以通过编译,哪些可以扮演成功做什么。
写个 static的swim()方法,让会游的东西都游起来,在不会使用接口多态语法时:

public static void doSwim(Fish fish){
    fish.swim;
}

public static void doSwim(Human human){
    fish.swim;
}
public static void doSwim(Submarine submarine){
    fish.swim;
}

鱼类继承自Fish所以public static void doSwim(Fish fish){fish.swim;} 无论什么鱼都可以使用Fish的doSwim。Human、Submarine各自接受各自的。
但是如果种类很多,就过于冗杂,其实如果操作同一个接口可以这样:

public static void main(String[] args) {
    doSwim(new Shark("ggb"));
    doSwim(new Human("bbg"));
    static void doSwim(Swimmer swimmer){
        swimmer.swim;
    }
}

只要是操作Swimmer接口的对象都可以使用doSwim()方法。
如果是海上飞机可以飞也可以下海那么:

public interface Flyer {
    public abstract void fly();
}
public class SeaPlane implements Swimmer,Flyer{
    protected String name;
    public SeaPlane (String name) {
        this.name=name;
    }
    public String getName() {
        return name;
    }
    @Override
    public abstract void swim(){
        System.out.printf("海上飞机%s 海上航行%n",name);
    }
    @Override
    public abstract void fly(){
        System.out.printf("海上飞机%s 飞行%n",name);
    }
}

在Java中,类可以操作两个以上的类,也就是拥有两种以上的行为。例如, Seaplane就同时拥有 Swimmer与 Flyer的行为。
如果是会游也会飞的飞鱼呢?飞鱼是一种鱼,可以继承Fsh类,飞鱼会飞,可以操作 Flyer接口

public class Flyingfish extends Fish implements Flyer{
    ...........
}

正如范例所示,在Java中,类可以同时继承某个类,并操作某些接口。如果现在要让所有会游的东西游泳,那么刚刚的 doSwim()方法就可以满足需求了,因为 Seaplane拥有 Swimmer的行为,而 Flyingfish也拥有 Swimmer的行为。
就满足目前需求来说,所做的就是新增程序代码来满足需求,但没有修改旧有既存的程序代码,你的程序确实拥有某种程度的弹性与可维护性。
当然需求是无止境的,原有程序架构也许确实可满足某些需求,但有些需求也可能超过了原有架构预留的弹性,一开始要如何设计才会有弹性,是必须靠经验与分析判断,不用为了保有程序弹性的弹性而过度设计。因过大的弹性表示过度预测需求,有的设计也许从不会遇上事先假设的需求。
例如,也许你预先假设会遇上某些需求而设计了一个接口,但从程序开发至生命周期结束,该接口从未被操作过,或者仅有一个类操作过该接口,那么该接口也许就不必存在你事先的假设也许就是过度预测需求,事先的设计也有可能因为需求不断增加,而超出原本预留的弹性。
不是所有人会游泳,有的飞机只会飞,所以假设游泳选手会游泳,Human不再操作Swimmer接口,新建游泳选手实例继承自Human并操作Swimmer。再设计Airplane类作为Seaplane的父类,Airplane操作Flyer,Seaplane继承Airplane后再操作Swimmer接口。

public class Seaplane extends Airplane implements Swimmer{
    ...........
}

再分成深海浅海游泳那么再来:

public interface Diver extends Swimmer{
        public abstract void dive();
}
public class Boat implements Swimmer{
    ...........
}
public class Submarine extends Boat implements Diver{
    public Submarine(String name){
        super(name);
    }
    @Override
    public abstract void dive(){
        .......
    }
}

需求不断变化,好的架构在修改时不会牵动全部代码—->设计的重要性。

阅读更多
版权声明:博主原创或改进文章 https://blog.csdn.net/zkd758/article/details/79952746
文章标签: Java
个人分类: Java Android
上一篇Java中关于垃圾收集和抽象类
下一篇接口语法细节(枚举常数)
想对作者说点什么? 我来说一句

没有更多推荐了,返回首页

关闭
关闭
关闭