關於接口的代碼複用

Head First 設計模式

軟件開發中,不變的真理就是Changejava

打開Head First設計模式,書上的第一個例子就讓我真正明白了咱們一直說的Java的接口優勢。設計模式

咱們常說:Java中類不容許多繼承,而採用了更加合理的接口設計。框架

繼承是爲了代碼複用的,而接口不過是聲明方法,那爲何接口能替代多繼承呢?這是我一直以來的疑問。今天,終於找到答案。ide

接口

注:如下代碼均省略getset方法。學習

原設計

/**
 * 動物
 */
public class Animal {

    private String name;      // 名稱

    public void eat() {}
}

/**
 * 鳥
 */
public class Bird extends Animal {

}

/**
 * 魚
 */
public class Fish extends Animal {
    
}

/**
 * 蝙蝠
 */
public class Bat extends Animal {

}

這是咱們的原設計,一個動物類,裏面有全部動物公有的eat方法,而後鳥、魚和蝙蝠都繼承自動物,複用了eat方法,減小了代碼量。this

需求變動

客戶改需求了,咱們要讓能飛的動物有fly方法,也就是咱們的BirdBat類生成的對象有要有fly這個方法。spa

而後這些飛的行爲都是相同的,咱們須要實現複用,最不負責任的方式就是在Animal類中添加fly方法,讓多個類能繼承這個方法,實現複用。設計

/**
 * 動物
 */
public class Animal {

    private String name;      // 名稱

    public void eat() {

    }
    
    public void fly() {
        System.out.println("I can fly");
    }
}

指正實現方式極其簡單,但會讓之後該代碼的人痛苦萬分。他們會去想,我new一個魚,怎麼給我提示一個fly方法?code

clipboard.png

如此,每次修改,都是對原有設計的破壞!

接口

/**
 * 接口:能飛的
 */
public interface Volitant {

    void fly();
}

/**
 * 鳥
 */
public class Bird extends Animal implements Volitant {

    @Override
    public void fly() {
        System.out.println("I can fly");
    }
}

/**
 * 蝙蝠
 */
public class Bat extends Animal implements Volitant {

    @Override
    public void fly() {
        System.out.println("I can fly");
    }
}

創建一個能飛的接口,讓這兩個須要飛的動物實現該接口,實現了須要飛的動物能飛,不須要飛的動物不能飛。對象

書中提出了一個思想:把會變化的部分取出並封裝起來,以便之後能夠輕易地改動或擴充此部分,而不影響不須要變化的其餘部門。

我對這句話的理解就是將常常須要改動的部分抽象出來並封裝,如此,用戶改動只改動相關部分,而對原設計沒有破壞。

可是這樣要怎麼實現代碼複用呢?一樣的代碼,在鳥和蝙蝠中寫了兩次。

複用

/**
 * 會飛的實現類
 */
public class VolitantImpl implements Volitant {
    @Override
    public void fly() {
        System.out.println("I can fly");
    }
}

/**
 * 鳥
 */
public class Bird extends Animal implements Volitant {

    private Volitant volitant = new VolitantImpl();

    @Override
    public void fly() {
        this.volitant.fly();
    }
}

/**
 * 蝙蝠
 */
public class Bat extends Animal implements Volitant {

    private Volitant volitant = new VolitantImpl();

    @Override
    public void fly() {
        this.volitant.fly();
    }
}

寫一個類,這個類去實現Volitant接口,而後在鳥和蝙蝠中引用這個實例化該類對象,調用其fly方法。如此,實現了代碼複用。固然這個實現方法很是的low,這裏直接去依賴實現類了,這就很差了。

控制反轉

這是我本身想的解決方法,歡迎批評指正。

/**
 * 鳥
 */
public class Bird extends Animal implements Volitant {

    @Autowired
    private Volitant volitant;

    @Override
    public void fly() {
        this.volitant.fly();
    }
}

寫了一段時間的Spring項目了,解除依賴的方法最經常使用的就是@Autowired,而後在實現類中添加@Service注入。

可是建的是普通的Java項目,咱們不能被@Autowired吊死,這是一種思想,一種控制注入類的思想。

/**
 * 鳥
 */
public class Bird extends Animal implements Volitant {

    private Volitant volitant;

    public void setVolitant(Volitant volitant) {
        this.volitant = volitant;
    }

    @Override
    public void fly() {
        this.volitant.fly();
    }
}

/**
 * 蝙蝠
 */
public class Bat extends Animal implements Volitant {

    private Volitant volitant;

    public void setVolitant(Volitant volitant) {
        this.volitant = volitant;
    }

    @Override
    public void fly() {
        this.volitant.fly();
    }
}

public class Main {

    public static void main(String[] args) {
        Volitant volitant = new VolitantImpl();
        Bird bird = new Bird();
        bird.setVolitant(volitant);
        bird.fly();
        Bat bat = new Bat();
        bat.setVolitant(volitant);
        bat.fly();
    }
}

爲兩個須要飛的動物添加set方法,用於注入實現,在main方法中實例化的時候將須要的實現注入進去。

其實Spring的注入也與之相似,只是其更增強大,在程序運行時自動掃描須要注入的地方,而後注入實現。此處,不過是手工注入,咱們學習到這種思想就是最大的收穫。

Java8

其實從Java8開始,能夠在接口中對方法進行默認的實現。

/**
 * 接口:能飛的
 */
public interface Volitant {

    default void fly() {
        System.out.println("I can fly");
    }
}

public class Main {

    public static void main(String[] args) {
        Bird bird = new Bird();
        bird.fly();
        Bat bat = new Bat();
        bat.fly();
    }
}

在接口中聲明一個默認的方法,若是沒有實現,則調用默認方法。

最佳實踐

這是一句在騰訊雲+社區一篇博客中借來的話。

clipboard.png

框架,就是對設計模式的最佳實踐。而咱們學習一個框架,不是去爲了學多麼新多麼新的技術,而是學習其愈來愈好的設計模式。一個框架的流行,不是沒有緣由的。
相關文章
相關標籤/搜索