重構代碼,使用抽象類,還是接口?

問題描述

在代碼重構時,我們經常會遇到這樣一個問題,對於多個相似的類,需要把通用的或共同的行爲抽離出來,到底應該抽象成類,還是接口?想要確定結果,還需再溫習一下抽象類和接口的概念,以及它們之間的異同。

一、抽象類

1、抽象類(Abstract Class)的定義

在Java中被abstract關鍵字修飾的類稱爲抽象類,被abstract關鍵字修飾的方法稱爲抽象方法,抽象方法只有方法的聲明,沒有方法體。抽象類可以包含屬性、方法、構造方法,但是構造方法不能用於實例化,主要用來被子類調用。

2、抽象類特點

抽象類中的抽象方法的修飾符默認public,可以爲protected;
抽象類不能被實例化,只能用作子類的超類;
如果一個類繼承了抽象類,那麼它必須實現抽象類中的所有抽象方法;
如果一個抽象類繼承了另一個抽象類,則無需實現父抽象類中的抽象方法;
注意事項:包含抽象方法的一定是抽象類,但是抽象類不一定包含抽象方法。

3、抽象類的應用場景

抽象類是用來捕捉子類的通用特性的。它是繼承層級裏子類的模板,被普通類和抽象類繼承。如果多個類的某個部分的功能相同,那麼可以抽象出一個類出來,把它們的相同部分都放到父類裏,讓它們都繼承這個類。對於子類和父類都有的成員變量和方法,遵循就近原則,自己有就優先訪問自己的。

代碼示例:

/**
 * 抽象類--陸地動物,多實現
 * @author FengJuan
 * @date 2022年10月8日
 * @Description 
 *
 */
public abstract class AbstractLandAnimal implements IAnimal,IBiology{

    public AbstractLandAnimal(){
        System.out.println("這是抽象的");
    }
    
    /**
    * 行走
    */
    abstract void walk();
}

4、擴展一下面向對象的三大特徵

4.1、封裝(Encapsulation)

封裝是將抽象得到的屬性和行爲相結合,形成一個有機的整體,形成一個“類”,其中屬性和行爲都是類的成員。當你在寫一個類的時候,其實就是在做封裝。封裝可以被認爲是一個保護屏障,防止該類的數據和行爲被外部類定義的代碼隨機訪問。
封裝的優點:

良好的封裝能夠減少耦合;
類內部的結構可以自由修改;
可以對成員變量進行更精確的控制;
隱藏信息和實現細節。

4.2、繼承(Inheritance)

繼承就是從已有的類中派生出新的類,新的類能吸收已有類的數據屬性和行爲,並能擴展新的能力。繼承需要考慮里氏替換原則。里氏替換原則爲所有引用基類的地方必須能透明地使用其子類的對象。

繼承的作用:
共性的抽取,代碼的複用;
有了繼承纔有下面的"方法的覆蓋"和"多態機制"。

4.2.1、重寫

方法的覆蓋,即重寫,在Java的子類與父類中有兩個名稱、參數列表相同的方法的情況。由於它們具有相同的方法簽名,所以子類中的新方法將覆蓋父類中原有的方法。

4.2.2、重載

重載,是指方法有相同的名稱,但是參數列表不相同的情形,這樣的同名不同參數方法之間,互相稱之爲重載方法。

4.3、多態

多態存在的三個必要條件:繼承、重寫、父類引用指向子類對象。
多態是同一個行爲具有多個不同表現形式或形態的能力,是一個引用變量到底會指向哪個類的實例對象,該引用變量發出的方法調用到底是哪個類中實現的方法,必須在由程序運行期間才能決定。

多態最簡單的理解就是:當使用多態方式調用方法時,首先檢查父類中是否有該方法,如果沒有,則編譯錯誤;如果有,再去調用子類的該同名方法。

代碼示例:

/**
 * 狗的封裝,單繼承
 * @author 程就人生
 * @date 2022年10月8日
 * @Description 
 *
 */
public class Dog extends AbstractLandAnimal{

    public Dog(){
        System.out.println("一個可愛的狗子誕生了");
        // 調用接口的靜態方法,只能以接口的名義調用
        IAnimal.breathe();
    }
    
    @Override
    public void eat() {
        System.out.println("吃了一個雞腿,雞腿太鹹");
        // 接口默認方法的調用
        drinkWater();
    }
    
    @Override
    void walk() {
        System.out.println("四條腿跑得快");
    }
}

二、接口(Interface)

1、定義

在Java中被Interface關鍵字修飾的類稱爲接口。接口是一系列方法的聲明,一個接口只有方法的特徵沒有方法的實現,因此這些方法可以在不同的地方被不同的類實現,而這些實現可以具有不同的行爲(功能)。因此接口就是抽象方法的集合。

2、特點

接口不能用於實例化對象,沒有構造方法;
接口不是被類繼承,只能被類實現;
接口只能繼承接口,支持多繼承;
接口中的每一個方法會被隱式的指定爲 public abstract;
接口中的每一個變量會被隱式的指定爲 public static final;
接口中的方法在JDK1.8版本之前是不能在接口中實現的,只能由實現接口的類來實現接口中的方法;

但在JDK1.8及以後版本添加了兩個例外:

默認方法:JDK 1.8允許給接口添加非抽象的方法實現,但必須使用default關鍵字修飾;定義了default的方法可以不被實現子類所實現,但只能被實現子類的對象調用;如果子類實現了多個接口,並且這些接口包含一樣的默認方法,則子類必須重寫默認方法;

靜態方法:JDK 1.8允許使用static關鍵字修飾一個方法,並提供實現,稱爲接口靜態方法。接口靜態方法只能通過接口調用。

代碼示例:

/**
 * 動物接口
 * @author 程就人生
 * @date 2022年10月8日
 * @Description 
 */
public interface IAnimal {
    /**
    * 默認 public abstract
    */
    void eat();    
    
    /**
    * 默認方法--喝水
    */
    default void drinkWater(){
    System.out.println("喝水");
    }    
    
    /**
    * 靜態方法--呼吸
    */
    static void breathe(){
    System.out.println("每時每刻都要呼吸");
    }
}

3、業務場景

如果多個類處理的目標是一樣的,但是處理的方法方式不同,那麼就定義一個接口,也就是一個標準,讓它們來實現這個接口,各自實現自己具體的處理方法來處理那個目標。

三、抽象類 VS 接口

1、抽象類和接口的相同點

抽象類和接口都不能被實例化;
接口的實現類實現了接口中的方法,抽象類的子類實現了抽象類中的抽象方法,接口的實現類和抽象類的子類才能實例化。

2、抽象類和接口的區別

一個類只能繼承一個抽象類,而一個類卻可以實現多個接口;
抽象類作爲很多子類的父類,它是一種模板式設計;而接口是一種行爲規範,它是一種輻射式設計。
抽象類是對類的抽象,而接口是對行爲的抽象;
抽象類是對整個類整體進行抽象包括屬性、行爲,而接口是對類局部(行爲)進行抽象;
繼承的根本原因是因爲要複用,而實現的根本是需要定義一個標準。

最 後 總 結

在重構代碼時,要看我們的業務場景。如果只是爲了代碼複用,屬性的複用,方法的複用,那就使用繼承,抽象成抽象類或普通類;如果只是對行爲的抽象,定義一個標準,實現又各不相同,那就使用實現,定義一個接口標準。

以上便是我的整理,希望對你有幫助。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章