菜鸟成长系列-多态、接口和抽象类

面向对象的三大特性:封装、继承、多态。从必定角度来看,封装和继承几乎都是为多态而准备的。这是咱们最后一个概念,也是最重要的知识点。html

多态的定义:指容许不一样类的对象对同一消息作出响应。即同一消息能够根据发送对象的不一样而采用多种不一样的行为方式。(发送消息就是函数调用)java

动态绑定

  • 静态绑定和动态绑定
    这里所谓的绑定,即一个方法的调用与方法所在的类(方法主体)关联起来。程序员

    静态绑定(前期绑定):即在程序执行前,即编译的时候已经实现了该方法与所在类的绑定,像C就是静态绑定。
    java中只有static,final,private和构造方法是静态绑定,其余的都属于动态绑定,而private的方法其实也是final方法(隐式),而构造 方法实际上是一个static方法(隐式),因此能够看出把方法声明为final,第一可让他不被重写,第二也能够关闭它的动态绑定。编程

    动态绑定(后期绑定):运行时根据对象的类型进行绑定,java中的大多数方法都是属于动态绑定,也就是实现多态的基础。
    java实现了后期绑定,则必须提供一些机制,可在运行期间判断对象的类型,并分别调用适当的方法。 也就是说,编译的时候该方法不与所在类绑定,编译器此时依然不知道对象的类型,但方法调用机制能本身去调查,找到正确的方法主体。java里实现动态绑定的是JVM.设计模式

动态绑定是实现多态的技术,是指在执行期间判断所引用对象的实际类型,根据其实际的类型调用其相应的方法。bash

多态的做用

消除类型之间的耦合关系。即:把不一样的子类对象都看成父类来看,能够屏蔽不一样子类对象之间的差别,写出通用的代码,作出通用的编程,以适应需求的不断变化。app

多态存在的三个必要条件

1、要有继承;
2、要有重写;
3、父类引用指向子类对象。ide

多态的优势

1.可替换性(substitutability)。多态对已存在代码具备可替换性。
2.可扩充性(extensibility)。多态对代码具备可扩充性。增长新的子类不影响已存在类的多态性、继承性,以及其余特性的运行和操做。实际上新加子类更容易得到多态功能。
3.接口性(interface-ability)。多态是超类经过方法签名,向子类提供了一个共同接口,由子类来完善或者覆盖它而实现的。
4.灵活性(flexibility)。它在应用中体现了灵活多样的操做,提升了使用效率。
5.简化性(simplicity)。多态简化对应用软件的代码编写和修改过程,尤为在处理大量对象的运算和操做时,这个特色尤其突出和重要。函数

多态的实现方式

Java中多态的实现方式:工具

  • 接口实现
  • 继承父类进行方法重写
  • 同一个类中进行方法重载。

    例子

    不管工做仍是学习中,笔都是咱们常常用到的工具。可是笔的种类又很是的繁多,铅笔、签字笔、水笔、毛笔、钢笔...。如今咱们要对“笔”进行抽象,抽象成一个抽象父类“Pen”
package com.glmapper.demo.base;

/**
 * 抽象父类:笔
 * @author glmapper
 */
public abstract class Pen {
    //笔的长度
    private int length;
    //颜色
    private String color;
    //类型
    private String type;
    //价格
    private double price;

    //写字
    public abstract void write(String cnt);

    public int getLength() {
        return length;
    }
    public void setLength(int length) {
        this.length = length;
    }
    public String getColor() {
        return color;
    }
    public void setColor(String color) {
        this.color = color;
    }
    public String getType() {
        return type;
    }
    public void setType(String type) {
        this.type = type;
    }
    public double getPrice() {
        return price;
    }
    public void setPrice(double price) {
        this.price = price;
    }

}复制代码

如今有两个子类,分别是:铅笔和钢笔。

铅笔类,继承父类Pen,并重写write方法

package com.glmapper.demo.base;
/**
 * 铅笔类 继承父类 笔(知足必要条件一:有继承【其实若是是接口的话,implement实现也是能够的】)
 * @author glmapper
 *
 */
public class Pencil extends Pen{
    /**
     * 父类的抽象方法委托子类具体实现:覆盖
     */
     //知足必要条件二:要有重写【固然,若是是对于write有重载也是能够的,不一样的概念而已】
    @Override
    public void write(String cnt) {
        System.out.println("这是一只铅笔写的内容,内容是:"+cnt);
    }

}复制代码
  • 钢笔类,继承父类Pen,并重写write方法
package com.glmapper.demo.base;
/**
 * 钢笔类 继承父类 笔
 * @author 17070738
 *
 */
public class Fountainpen extends Pen{

    @Override
    public void write(String cnt) {
        System.out.println("这是一支钢笔写的内容,内容是:"+cnt);
    }

}复制代码

测试:

package com.glmapper.demo.base;

public class MainTest {

    public static void main(String[] args) {

    /*    Pen pen= new Pencil();*/
        //必要条件三:父类引用指向子类对象。
        Pen pen= new Fountainpen();
        pen.write("我是一支笔");

    }
}复制代码

输出结果:这是一支钢笔写的内容,内容是:我是一支笔

说明

可替换性:多态对笔Pen类工做,对其余任何子类,如铅笔、钢笔,也一样工做。
可扩充性:在实现了铅笔、钢笔的多态基础上,很容易增添“笔”类的多态性。

接口

一个Java接口,就是一些方法特征的集合。【本文角度并不是是java基础角度来讲,主要是以设计模式中的应用为背景,所以对于相关定义及用法请自行学习。www.runoob.com/java/java-i…
咱们在平时的工做中,提到接口,通常会含有两种不一样的含义,

  • 指的是java接口,这是一种java语言中存在的结构,有特定的语法和结构
  • 指一个类所具备的方法特征的集合,是一种逻辑上的抽象。

前者叫作“java接口”,后者叫着“接口”。例如:java.lang.Runnable就是一个java接口。

为何使用接口

咱们考虑一下,假如没有接口会怎么样呢?一个类总归是能够经过继承来进行扩展的,这难道不足以咱们的实际应用吗?
一个对象须要知道其余的一些对象,而且与其余的对象发生相互的做用,这是由于这些对象须要借住于其余对象的行为以便于完成一项工做。这些关于其余对象的知识,以及对其余对象行为的调用,都是使用硬代码写在类里面的,可插入性几乎为0。如:钢笔中须要钢笔水,钢笔水有不一样的颜色:
钢笔水类:

package com.glmapper.demo.base;
/**
 * 钢笔墨水
 * @author glmapper
 */
public class PenInk {
    //墨水颜色
    private String inkColor;

    public String getInkColor() {
        return inkColor;
    }

    public void setInkColor(String inkColor) {
        this.inkColor = inkColor;
    }

    public PenInk(String inkColor) {
        super();
        this.inkColor = inkColor;
    }

}复制代码

钢笔中持有一个墨水类的对象引用:

package com.glmapper.demo.base;
/**
 * 钢笔类 继承父类 笔
 * @author 17070738
 *
 */
public class Fountainpen extends Pen{
    //引用持有
    PenInk ink =new PenInk("black");
    @Override
    public void write(String cnt) {
        System.out.println("钢笔墨水颜色是:"+ink.getInkColor());
        System.out.println("这是一支钢笔写的内容,内容是:"+cnt);
    }
}复制代码

可是这种时候,咱们须要换一种颜色怎么办呢?就必需要对Fountainpen中的代码进行修改,将建立PenInk对象时的inkColor属性进行更改;如今假如咱们有一个具体的类,提供某种使用硬代码写在类中的行为;
如今,要提供一些相似的行为,而且能够实现动态的可插入,也就是说,要可以动态的决定使用哪种实现。一种方案就是为这个类提供一个抽象父类,且声明出子类要提供的行为,而后让这个具体类继承自这个抽象父类。同时,为这个抽象父类提供另一个具体的子类,这个子类以不一样的方法实现了父类所声明的行为。客户端能够动态的决定使用哪个具体的子类,这是否能够提供可插入性呢?
改进以后的代码:
子类1:黑色墨水

package com.glmapper.demo.base;
/**
 * 黑色墨水
 * @author glmapper
 */
public class BlackInk extends PenInk{

    public BlackInk() {
        super("black");
    }
}复制代码

子类2:蓝色墨水

package com.glmapper.demo.base;
/**
 * 蓝色墨水
 * @author glmapper
 */
public class BlueInk extends PenInk{

    public BlueInk() {
        super("blue");
    }
}复制代码

钢笔类引用:

package com.glmapper.demo.base;
/**
 * 钢笔类 继承父类 笔
 * @author 17070738
 *
 */
public class Fountainpen extends Pen{
    PenInk ink ;
    //经过构造函数初始化PenInk ,PenInk由具体子类来实现
    public Fountainpen(PenInk ink) {
        this.ink = ink;
    }
    @Override
    public void write(String cnt) {
        System.out.println("钢笔墨水颜色是:"+ink.getInkColor());
        System.out.println("这是一支钢笔写的内容,内容是:"+cnt);
    }
}复制代码

客户端调用:

public static void main(String[] args) {
        /**
         * 使用黑色墨水子类
         */
        Pen pen= new Fountainpen(new BlackInk());
        pen.write("我是一支笔");

    }复制代码

从上面代码能够看出,确实能够在简单的状况下提供了动态可插入性。

可是因为java语言是一个单继承的语言,换言之,一个类只能有一个超类,所以,在不少状况下,这个具体类可能已经有了一个超类,这个时候,要给他加上一个新的超类是不可能的。若是硬要作的话,就只好把这个新的超类加到已有的超类上面,造成超超类的状况,若是这个超超类的位置也已经被占用了,就只好继续向上移动,直到移动到类等级结构的最顶端。这样一来,对一个具体类的可插入性设计,就变成了对整个等级结构中全部类的修改。这种仍是假设这些超类是咱们能够控制的,若是某些超类是由一些软件商提供的,咱们没法修改,怎么办呢?所以,假设没有接口,可插入性就没有了保证。

类型

java接口(以及java抽象类)用来声明一个新的类型。
java设计师应当主要使用java接口和抽象类而不是具体类进行变量的类型声明、参数的类型声明、方法的返还类型声明,以及数据类型的转换等。固然,一个更好的作法是仅仅使用java接口,而不要使用抽象java类来作到上面这些。在理想的状况下,一个具体java类应当只实现java接口和抽象类中声明过的方法,而不该该给出多余的方法。

  • 类型等级结构
    java接口(以及抽象类)通常用来做为一个类型的等级结构的起点
    java的类型是以类型等级结构的方式组织起来的,在一个类型等级结构里面,一个类型能够有一系列的超类型,这时这个类型叫作其超类型的子类型。子类型的关系是传递性:类型甲是类型乙的子类型,类型乙是类型丙的子类型,那么类型甲就是类型丙的子类型。
  • 混合类型
    若是一个类已经有一个主要的超类型,那么经过实现一个接口,这个类能够拥有另外一个次要的超类型。这种次要的超类型就叫作混合类型。例如:在java中,

TreeMap类有多个类型,它的主要类型是AbstractMap,这是一种java的汇集;而Cloneable接口则给出了一个次要类型,这个类型说明当前类的对象是能够被克隆;同时Serializable也是一个次要类型,它代表当前类的对象是能够被序列化的。而NavigableMap继承了SortedMap,由于以前说到过,子类型是能够传递的,所以对于TreeMap来讲,SortedMap(或者说NavigableMap)代表这个汇集类是能够排序的。

接口的一些用法

  • 单接口方法:接口中只有一个方法;java语言中有不少但方法接口的使用,Runnalble接口中的run()方法。
    public interface Runnable {
      /**
       * When an object implementing interface <code>Runnable</code> is used
       * to create a thread, starting the thread causes the object's * <code>run</code> method to be called in that separately executing * thread. * <p> * The general contract of the method <code>run</code> is that it may * take any action whatsoever. * * @see java.lang.Thread#run() */ public abstract void run(); }复制代码
  • 标识接口:没有任何方法和属性的接口;标识接口不对实现它的类有任何语义上的要求,仅仅是代表实现该接口的类属于一个特定的类型。上面说到的Serializable接口就是一种标识接口。
public interface Serializable {
}复制代码
  • 常量接口:用java接口来声明一些常量
package com.glmapper.demo.base;

public interface MyConstants {
    public static final String USER_NAME="admin";
};复制代码

这样一来,凡是实现这个接口的类都会自动继承这些常量,而且均可以像使用本身的常量同样,不须要再用MyConstants.USER_NAME来使用。

抽象类

在java语言里面,类有两种,一种是具体类,一种是抽象类。在上面给出的代码中,使用absract修饰的类为抽象类。没有被abstract修饰的类是具体类。抽象类一般表明一个抽象概念,它提供一个继承的出发点。而具体类则不一样,具体类能够被实例化,应当给出一个有逻辑实现的对象模板。因为抽象类不能够被实例化,所以一个程序员设计一个新的抽象类,必定是用来被继承的。(不建议使用具体类来进行相关的继承)。

关于代码重构

假设有两个具体类,类A和类B,类B是类A的子类,那么一个比较简单的方案应该是创建一个抽象类(或者java接口),暂定为C,而后让类A和类B成为抽象类C的子类【没有使用UML的方式来绘制,请见谅哈】。


上面其实就是里氏替换原则,后面会具体介绍到的。这种重构以后,咱们须要作的就是如何处理类A和类B的共同代码和共同数据。下面给出相关准则。

  • 抽象类应当拥有尽量多的共同代码


在一个继承等级结构中,共同的代码应当尽可能向结构的顶层移动,将重复的代码从子类中抽离,放在抽象父类中,提升代码的复用率。这样作的另一个好处是,在代码发生改变时,咱们只须要修改一个地方【由于共同代码均在父类中】。

  • 抽象类应当拥有尽量少的数据
    数据的移动方向是从抽象类到具体类,也就是从继承等级的顶层到底层的移动。咱们知道,一个对象的数据不管是否使用都会占用资源,所以数据应当尽可能放到具体类或者继承等级结构的低端。

Has - A 与Is -A

当一个类是另一个类的角色时【我 有一个 玩具】,这种关系就不该当使用继承来描述了,这个将会在后面说到的“合成/聚合复用原则”来描述。
Has - A: 我有一只笔(聚合)
Is - A:钢笔是一种笔(继承)

关于子类扩展父类的责任

子类应当扩展父类的职责,而不是置换掉或者覆盖掉超类的职责。若是一个子类须要将继承自父类的责任取消或者置换后才能使用的话,就颇有可能说明这个子类根本不属于当前父类的子类,存在设计上的缺陷。

最后,说明下,咱们在平时的工做中会常用的工具类,再次特意申明一下,咱们也尽量少的去从工具类进行继承扩展。

参考:

相关文章
相关标签/搜索