菜鳥成長系列-多態、接口和抽象類

面向對象的三大特性:封裝、繼承、多態。從必定角度來看,封裝和繼承幾乎都是爲多態而準備的。這是咱們最後一個概念,也是最重要的知識點。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:鋼筆是一種筆(繼承)

關於子類擴展父類的責任

子類應當擴展父類的職責,而不是置換掉或者覆蓋掉超類的職責。若是一個子類須要將繼承自父類的責任取消或者置換後才能使用的話,就頗有可能說明這個子類根本不屬於當前父類的子類,存在設計上的缺陷。

最後,說明下,咱們在平時的工做中會常用的工具類,再次特意申明一下,咱們也儘量少的去從工具類進行繼承擴展。

參考:

相關文章
相關標籤/搜索