2020-02-01 第 20 章 接口的定义与使用

使用抽象类和接口进行灵活的代码设计,意味着对面向对象设计概念有了充分的理解。这是需要大量的程序代码累计而成的。

接口的基本定义

抽象类与普通类相比,最大的优势,是可以实现对子类覆写方法的控制。但是在抽象类中依然会保留普通方法。而普通方法里可能会涉及到安全或隐私的操作问题。在开发过程中,如果要想对外隐藏全部的实现细节,则可以通过接口来进行描述。具体在之后的“分布式”开发中,会有具体的解释说明。

接口可以理解为一个纯粹的抽象类,在最原始的定义中,接口中只包含有抽象方法和全局常量。但是,从 JDK 1.8 开始,由于引入了 Lambda 表达式的概念,所以接口的定义也得到了加强,除了抽象方法与全局常量之外,还可以定义普通方法和静态方法。如果从设计本身的角度来讲,接口之中的组成还应该以抽象方法和全局常量为主

在 Java 中,接口主要使用 interface 关键字来定义。接口的命名,约定俗成的使用大写字母 “I”(Interfacce 的缩写)开头,后面跟上普通类的命名方式(各单词首字母大写)。如:

interface IMessage{
  public static final String INFO = "hello";
  public abstract void getInfo();
}

此时的接口肯定无法直接产生实例化对象,因为有抽象方法,并没有实现。所以对于接口的使用,原则如下:

  • 接口需要被子类实现(implements),一个子类可以实现多个接口;
  • 子类如果不是抽象类,那么一定要覆写接口之中的全部抽象方法;
  • 接口对象可以利用子类对象的向上转型进行实例化。

接口的实现,一般在普通的类名后跟上 ”Impl“,表示接口的实现。

在上例的代码中,INFO 常量可以直接通过 IMessage.INFO 调用,因为是全局常量。

getInfo() 方法必须要在子类中实例化后使用。如:

class MessageImpl implements IMessage{
  public void getInfo(){
    System.out.println("Hello World!");
  }
}

使用时:

String str = IMessage.INFO;
IMessage msg = new MessageImpl();
msg.getInfo();

在 Java 中之所以使用接口主要的目的是一个子类可以实现多个接口。利用接口可以实现多继承的概念。 如:

class MessageImpl implements IMessage, IChannel{}

实际举例

接口的概念类似现实生活中的"协议标准"的概念。如:USB 接口协议。凡是采用 USB 接口协议的设备,都可以根据协议,传输数据。

接口的转型问题

实现(多个)接口的子类,既是(多个)接口的子类,同时还是 Object 类的子类。

所以,这个子类的对象,可以向上转型成(多个)接口对象,也可以向上转型成 Object 对象。同理,向上转型后,可以向下转型成(多个中任意一个)接口的对象。

另外 ,在 Java 程序中,接口不允许继承父类。接口不会是 Object 的子类。

所以,可以接收 Object 类对象,就意味着可以接收所有的数据类型:基本数据类型、类对象、接口对象、数组。

访问权限问题

由于接口描述的是公共的定义标准,所以在接口中所有抽象方法的访问权限,都为 public。

虽然可以省略,部分关键字,但是,推荐用上述的方法定义接口中的全局常量与抽象方法。如下重复:

interface 接口名称{
  public static final 全局常量类型 全局常量名 = 内容;
  public abstract 返回值类型 方法名;
}

抽象类与接口

区别
序号 区别 抽象类 接口
1 定义 abstract class 抽象类名称 {} interface 接口名称 {}
2 组成 构造方法、普通方法、静态方法、全局常量、属性、成员常量 抽象方法、全局常量(普通方法、static 方法)
3 权限 各种权限定义 只能使用 public
4 子类使用 子类通过 extends 继承一个父类。 子类通过 implements 继承多个父接口。
5 两者关系 抽象类可以实现多个接口。 接口不允许继承抽象类,但是允许继承多个父接口。
6 继承与实现 (同普通类)继承一个父类,实现多个接口。 继承多个接口。
共同点
  • 抽象类和接口必须定义子类;
  • 子类必须覆写父类或父接口的所有抽象方法;
  • 通过子类的向上转型实现抽象类或接口的对象实例化。
另外
  • 虽然一个接口无法继承一个父类,但是一个接口可以通过 extends 继承若干个父接口,称为接口的多继承。
  • 先继承后实现,先 extendsimplements
  • 当抽象类和接口都可以使用的情况下,优先考虑接口,因为接口可以避免子类的单继承局限。
    从代码设计角度而言,也需要通过接口,对项目进行整体的设计。
组合使用

若接口中的方法在多个子类中有完全相同的实现,则可以在接口与普通子类中增加一个抽象类,将其相同的方法实现后,通过子类继承的形式使用。如:

interface IMessage {
  public abstract void f1();
  public abstract void f2();
}
abstract class MessageAbstractImpl {
  public void f1(){
    // ...
  }
}
class MessageImplA extends MessageAbstractImpl implements IMessage{
  public void f2(){ // ... }
  // ...
}
class MessageImplB extends MessageAbstractImpl implements IMessage{
  public void f2(){ // ... }
}
class MessageImplC implements IMessage{
  public void f1(){ // ... }
  public void f2(){ // ... }
}

实际用处

在实际的开发过程中,接口的使用往往有三种形式:

  • 进行标准设置;
  • 表示一种操作能力;
  • 暴露远程方法视图,一般在 RPC 分布式开发中使用。

重要的设计模式

工厂设计模式

当一个接口有多个类的去实现的时候,可以通过工厂设计,将类的实例化置于主客户端之外。具体操作:

定义一个 Factory 类,内部定义 public static 的 getInstance() 方法,需要接收参数:String className。代码如下(接 组合使用 中的代码):

class Factory{
  public static IMessage getInstance(String className){
    if ("MessageImplA".equals(className)){
      return new MessageImplA();
    }
    if ("MessageImplB".equals(className)){
      return new MessageImplB();
    }
    if ("MessageImplC".equals(className)){
      return new MessageImplC();
    }
  }
}

主类(客户端)中,通过 Factory.getInstance() 创建接口的子类并向上转型,得到接口的实现。如下:

IMessage msg = Factory.getInstance("MessageImplB");
代理设计模式

当一个接口实现的调用前后,需要执行许多其他的方法,如准备、检查参数格式与数量、收尾等,不重要但是又必须,适合与该接口实现的调用一同捆绑打包,则可以用一个继承接口的 Proxy 将代码捆绑置于主客户端之外。举例如下(接 组合使用 中的代码):

class Proxy implements IMessage{
  private IMessage msg;
  
  public Proxy(IMessage msg){
    this.msg = msg;
  }
  
  public void f1(){ // 覆写接口方法
    // ...
    // ...
    this.mgs.f1();
    // ...
  }
}

主类(客户端)中:

IMessage msg = new Proxy(new MessageImplB());
工厂设计模式与代理设计模式的结合

保留上述所有代码,最后主类中的代码替换为:

IMessage msg = new Proxy(Factory.getInstance("MessageImplB"));

即可将接口的实例化与其次要功能捆绑,置于主客户端之外。具体可参考下部分实例,了解其实际运用。

题:模拟绘图。

结果:

2020-02-01 第 20 章 接口的定义与使用_第1张图片
工厂设计模式与代理设计模式的结合 实例

代码实现:

interface IDraw{
    public abstract void draw(double... numbers);
}

class Point{
    private double x;
    private double y;

    public Point(double x, double y){
        this.setX(x);
        this.setY(y);
    }

    public void setX(double x){
        this.x = x;
    }
    public void setY(double y){
        this.y = y;
    }
    public double getX(){
        return this.x;
    }
    public double getY(){
        return this.y;
    }
    public double[] getPoint(){
        return new double[]{this.getX(), this.getY()};
    }

    @Override
    public boolean equals(Object anObject){
        if (this == anObject){
            return true;
        }else{
            if (anObject instanceof Point){
                Point aPoint = (Point) anObject;
                if (this.getX() == aPoint.getX() &&
                        this.getY() == aPoint.getY()){
                    return true;
                }
            }
        }
        return false;
    }

    @Override
    public String toString() {
        return "[" + this.getX() + "," + this.getY() + "]";
    }
}

class DrawCircle implements IDraw{
    @Override
    public void draw(double[] numbers) {
        Point point = new Point(numbers[0], numbers[1]);
        double radius = numbers[2];
        System.out.println("【DRAW CIRCLE START】");
        System.out.println("Center: " + point + ", radius: " + radius + ".");
        System.out.println("Draw a circle!");
        System.out.println("Complete!");
    }
}

class DrawTriangle implements IDraw{
    @Override
    public void draw(double[] numbers) {
        Point[] points = new Point[3];
        for (int n = 0; n <= 2; n++){
            points[n] = new Point(numbers[2 * n], numbers[2 * n + 1]);
        }
        System.out.println("【DRAW TRIANGLE START】");
        for (int n = 1; n <= 3; n++){
            if (n <= 2){
                System.out.println("Point" + n + ": " + points[n - 1]);
                System.out.println("Point" + (n + 1) + ": " + points[n]);
                System.out.println("Draw a line.");
            }else{
                System.out.println("Point" + n + ": " + points[n - 1]);
                System.out.println("Point" + 1 + ": " + points[0]);
                System.out.println("Draw a line.");
            }
        }
        System.out.println("Complete!");
    }
}

class DrawRectangle implements IDraw{
    @Override
    public void draw(double[] numbers) {
        Point startPoint = new Point(numbers[0], numbers[1]);
        double width = numbers[2];
        double height = numbers[3];
        Point[] points = new Point[]{
                startPoint,
                new Point(startPoint.getX() + width, startPoint.getY()),
                new Point(startPoint.getX() + width, startPoint.getY() + height),
                new Point(startPoint.getX(), startPoint.getY() + height)
        };
        System.out.println("【DRAW RECTANGLE START】");
        for (int n = 1; n <= 4; n++){
            if (n <= 3){
                System.out.println("Point" + n + ": " + points[n - 1]);
                System.out.println("Point" + (n + 1) + ": " + points[n]);
                System.out.println("Draw a line.");
            }else{
                System.out.println("Point" + n + ": " + points[n - 1]);
                System.out.println("Point" + 1 + ": " + points[0]);
                System.out.println("Draw a line.");
            }
        }
        System.out.println("Complete!");
    }
}

class DrawProxy implements IDraw{
    IDraw drawInstance;

    public DrawProxy(IDraw drawInstance){
        this.drawInstance = drawInstance;
    }

    @Override
    public void draw(double... numbers) {
        if (drawInstance instanceof DrawCircle){
            if (numbers.length != 3){
                System.out.println("输入的数字有误!");
            }else{
                drawInstance.draw(numbers);
            }
        }
        if (drawInstance instanceof DrawTriangle){
            if (numbers.length != 6){
                System.out.println("输入的数字有误!");
            }else{
                drawInstance.draw(numbers);
            }
        }
        if (drawInstance instanceof DrawRectangle){
            if (numbers.length != 4){
                System.out.println("输入的数字有误!");
            }else{
                drawInstance.draw(numbers);
            }
        }
    }
}

class DrawFactory{
    public static IDraw getInstance(String shape){
        if ("圆".equals(shape)){
            return new DrawCircle();
        }
        if ("矩形".equals(shape)){
            return new DrawRectangle();
        }
        if ("三角形".equals(shape)){
            return new DrawTriangle();
        }
        return null;
    }
}

public class Main {

    public static void main(String[] args) {
        printLineStart("Start");
        System.out.println();

        new DrawProxy(DrawFactory.getInstance("圆")).draw(0, 0, 1);
        System.out.println();
        new DrawProxy(DrawFactory.getInstance("圆")).draw(0, 0);
        System.out.println();
        new DrawProxy(DrawFactory.getInstance("矩形")).draw(0, 0, 1, 2);
        System.out.println();
        new DrawProxy(DrawFactory.getInstance("三角形")).draw(0, 0, 0, 1, 1, 2);

        System.out.println();
        printLineStart("End");
    }

    public static void printLineStart ( String diff ){
        switch (diff){
            case "Start":{
                System.out.println("------\t程序开始\t------");
                break;
            }
            case "End":{
                System.out.println("------\t程序结束\t------");
                break;
            }
            default:{
                return;
            }
        }
    }
}

你可能感兴趣的:(2020-02-01 第 20 章 接口的定义与使用)