面向对象基础(三)接口

活动地址:CSDN21天学习挑战赛

5. 接口

5.1 什么是接口

Interface,接口是Java语法中最强大,也最为让人迷惑误解的一种语法,在加之从Java 8这个版本以来,接口的语法有了一些变化和更新,使得不少程序员对于接口经常使用不当。

接口在设计之初,是为了能编写出松耦合易扩展可测试的应用程序。

//当在A类中,使用B类时,我们可以说,A依赖B,或者A与B有耦合
​
//当我们修改B类逻辑时,A类相应的也有很大可能要进行修改
​
//如果还有其它类依赖A的话,那么将会像多米诺骨牌一样,因为B的一处修改,整个依赖的生态链都会发生蝴蝶效应带来的修改
​
//即使是B类的修改,A类及其相关类无需修改任何逻辑也能正常运行,但是它们都需要被重新编译、重新部署,这都需要消耗系统资源和时间

针对以上场景,是否能有一种语法来降低类与类之间的耦合度,让其耦合度更松散一些(松耦合)呢?

就好比,小车的引擎坏了,只需要更换或者修理引擎就行了,而不需要动其它部件。

之前,我们学习过的抽象,即隐藏实现细节,只对外暴露必要的成员,使用private这样的修饰符,可以让A尽可能少的“认识”B,这样就能降低两者之间的耦合度。但这还不够,使用interface我们可以更进一步的来降低这种耦合度。

public interface Draggable {
  void drag();
}

接口就像是一个类,但是里面一般只定义方法,而且这个方法没有任何实现细节。换句话说,接口只定义它应该做什么。

要解藕AB的话,我们只需要在之间定义一个interface,当B的具体逻辑修改时,A将不再会受到影响,因为A根本就不了解B是如何实现的。这就是所谓的面向接口编程

  • interface,主要用来定义应该做什么(what),例如数据压缩、加密解密、排序、查找

  • class,主要用来定义具体怎么做(how)

5.2 高耦合度案例 - 个税计算

public class TaxCalculator {
​
    private double income;
​
    public TaxCalculator(double income) {
        this.income = income;
    }
​
    public double calculatorTax() {
        return income * 0.3;
    }
​
}
​
public class TaxReport {
​
    private TaxCalculator calculator;
​
    public TaxReport() {
        calculator = new TaxCalculator(10_000);
    }
​
    public void show() {
        var tax = calculator.calculatorTax();
        System.out.println(tax);
    }
}

这个案例由于TaxReport使用了TaxCalculator,造成了后者依赖前者,因此存在如下几个问题:

  • TaxCalculator的构造器或者方法的签名如果发生改变,那么将会导致另一方需要修改代码。

  • 即使第一种情况没有发生,但是计算个税的逻辑如果发生了细微调整,那么也会导致使用它的类重新编译。

5.3 创建于实现接口

public interface TaxCalculator {
​
    double calculateTax();
    
}

接口中的方法默认是publicabstract的。

重新修改TaxCalculator,使其实现这个接口:

public class TaxCalculatorImpl implements TaxCalculator {
​
    private double income;
​
    public TaxCalculatorImpl(double income) {
        this.income = income;
    }
​
    @Override
    public double calculateTax() {
        return income * 0.3;
    }
​
}

在实现接口时,必须实现接口中定义的所有方法,且方法的签名保持一致。

关于接口,还需要我们了解的是,我们可以让一个类在继承一个父类的同时,根据程序的需要,实现一个或者更多个接口。

5.4 依赖注入

Dependency Injection,又简称DI,根据这一原则,我们的类不应该实例化它的依赖。(不要依赖具体的逻辑)

TaxReport的构造器中,我们不应该自己实例化它的依赖,而只应该使用它,DI原则认为,实例化和使用是两件独立的事情,我们应该尽量分离两者。

例如:在餐厅中,厨师和服务员互相之间有依赖关系,但是服务员确定好客户的点膳后,需要厨师来制作膳食,他不应该自己去厨房制作,而只是把点膳的信息传递给厨师;反之,厨师做好膳食以后,需要把膳食递交给服务员,而不应该自己跑去大堂传膳。每个人的职责都是单一且专业的。

那么,在上述案例中,我们需要把实例化TaxCalculator的逻辑交给另一个类来完成,比如Main,然后传递这个实例给TaxReport使用。

这个传递依赖的具体实现(实例)给使用者的过程,就称之为依赖注入。

依赖注入有以下3种实现方式:

  • 构造器注入

  • Setter注入

  • 方法注入

5.4.1 构造器注入

public class TaxReport {
​
    private TaxCalculator calculator;
​
    public TaxReport(TaxCalculator calculator) {
        this.calculator = calculator;
    }
​
    public void show() {
        var tax = calculator.calculateTax();
        System.out.println(tax);
    }
}
​
public class Main {
​
    public static void main(String[] args) {
        var calculator = new TaxCalculatorImpl(10_000);
        var report = new TaxReport(calculator);
        report.show();
    }
    
}

5.4.2 Setter注入

public class TaxReport {
​
    private TaxCalculator calculator;
​
    public void setCalculator(TaxCalculator calculator) {
        this.calculator = calculator;
    }
​
    public void show() {
        var tax = calculator.calculateTax();
        System.out.println(tax);
    }
  
}
​
public class Main {
​
    public static void main(String[] args) {
        var calculator = new TaxCalculatorImpl(10_000);
        var report = new TaxReport();
        report.setCalculator(calculator);
        report.show();
    }
​
}

5.4.3 方法注入

public class TaxReport {
​
    public void show(TaxCalculator calculator) {
        var tax = calculator.calculateTax();
        System.out.println(tax);
    }
  
}
​
public class Main {
​
    public static void main(String[] args) {
        var calculator = new TaxCalculatorImpl(10_000);
        var report = new TaxReport();
        report.show(calculator);
    }
​
}

5.5 接口隔离原则

将包含很多方法的大接口切割为多个包含少量且必要方法的小接口。

public interface UIWidget {
​
    void drag();
    void resize();
​
}
​
public class Dragger {
​
    public void drag(UIWidget widget){
        widget.drag();
    }
​
}

当我们修改resize方法,例如给它增加参数,虽然Dragger没有使用这个方法,但也会导致这个类重新编译。

因此,在这种场景下,我们应该将drag方法单独封装到另一个接口去,使之与其它没有必要一起使用的方法隔离开来。

如果有一个场景,我们必须要同时使用两个方法,可以让相关接口形成继承关系来达到代码复用:

public interface Draggable {
    void drag();
}
​
public interface UIWidget extends Draggable {
    void resize();
}

与类的单根继承不同,一个接口可以继承多个接口。

5.6 Project - MyTube

见项目资源文件夹 Mytube.zip

请定义相关接口并调整项目代码,降低VideoProcessor与其依赖的耦合度。

5.7 接口新语法

  • 定义常量

  • 默认方法

  • 私有方法

5.8 接口与抽象类

5.9 面向接口编程的益处

  • 灵活切换实现方式

  • 是的程序更容易扩展,同时最小化扩展带来的负面影响

  • 更容易进行独立的单元测试

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值