Java编程思想读书笔记第九章:接口

抽象类和抽象方法

  • 抽象方法表示形式abstract void method(),仅仅只有声明没有方法体。
  • 包含抽象方法的类叫做抽象类,class前面添加关键字abstract,如public abstract class People
  • 一个类如果需要继承抽象类,必须要实现全部的抽象方法。

接口

  • 接口和类相比,所有方法只有声明,没有实现,关键字不是class而是interface。一个类可以通过关键字implements关键字。可以理解成接口一份房屋设计图,具体房屋怎么盖,用什么材料,都在类中具体实现。
  • 接口中方法默认都是public的,定义的变量,默认都是final static,接口不能被实例化。

完全解耦

  • 如果有一个方法操作的是一个类,那这个方法只能用于这个类或者该类的子类。如果你想把这个方法应用在不在此继承结构的某个类,那肯定是无法使用的。接口可以很大程度放宽这种限制,因此可以编写复用性更高的代码。
使用类的耦合性
  • 有个Bird类作为基类,老鹰Eagle和燕子Swallow都继承自Bird类,并且重载fly()方法,毕竟每种鸟类飞行方式不一样,Apply让这些鸟飞起来,调用对应的fly()方法。
public class Bird {
    public void fly() {
        System.out.println("Bird can fly");
    }
}

class Eagle extends Bird {
    @Override
    public void fly() {
        System.out.println("Eagle can fly");
    }
}

class Swallow extends Bird {
    @Override
    public void fly() {
        System.out.println("Swallow can fly");
    }
}

class Apply {
    public static void fly(Bird bird) {
        bird.fly();
    }
}
  • 下面是AirPlane类也具备飞行的能力,看起来是不是和上面的Bird差不多,但是我这时候也想复用Apply的代码,显然是不可能的,因为Apply中的fly方法已经牢牢和Bird绑定了,我如果想复用除非AirPlane也继承Bird,这在逻辑上显然是行不通的,飞机怎么能继承鸟类呢。
public class AirPlane {
    public void fly() {
        System.out.println("AirPlane can fly");
    }
}

class Helicopter extends AirPlane {
    @Override
    public void fly() {
        System.out.println("Helicopter can fly");
    }
}

class Fighter extends AirPlane {
    @Override
    public void fly() {
        System.out.println("Fighter can fly");
    }
}
使用接口解耦
  • 这时候我们抽象出一个飞行接口就可以解决上面的问题,然后让BirdAirPlane都实现飞行接口,将Apply的传递参数改为Fly接口类型,这样就实现了ApplyBird的解耦,如果以后再新建其他具有飞行能力的类都可以复用。
public interface Fly  {
    void fly();
}

public class Bird implements Fly {
    @Override
    public void fly() {
        System.out.println("Bird can fly");
    }
}

public class AirPlane implements Fly {
    @Override
    public void fly() {
        System.out.println("AirPlane can fly");
    }
}

public class Apply {
    public static void fly(Fly fly) {
        fly.fly();
    }
}
适配接口
  • 接口将声明和实现分开,同一个接口可以有多种实现方式。你只要编写一个方法用于接受接口,只要对象实现这个接口,就能被这个方法所使用,如上面的Apply中的Fly()方法。
  • 在现实开发中可能会遇到AirPlane是第三方jar包提供的,我们无法修改内部代码,我们可以使用适配器模式,在适配器中实现接口,充当一个转换器,从而可以复用代码。这种方式是针对在无法修改源码的情况,如果我们可以直接使用接口,就不需要用这种方式了。
public class AirPlaneAdapter implements Fly {
    private AirPlane airPlane;

    public AirPlaneAdapter(AirPlane airPlane) {
        this.airPlane = airPlane;
    }

    @Override
    public void fly() {
        airPlane.fly();
    }
}

Apply.fly(new AirPlaneAdapter(new AirPlane()));
  • 通过上面发现,我们可以在任意一个类上面添加接口,这意味着任何一个类都可以通过这种方式来适配方法。
困惑
  • 刚开始看书的时候,我觉得创建接口这么麻烦,为什么不直接在Apply再添加一个方法用来接受AirPlane类型。 这种做法从功能实现的角度来看也可以,毕竟我这样做也实现了功能嘛。
  • 从设计角度来看,不可取。结合实际项目想一下,如果这时候项目经理跟你说添加了3种具备飞行能力的类,原来的那个Bird类不用了,如果按照上面那种做法,我又得在Apply类中添加3个方法用于接受新建的类,再删除接收Bird类的方法。
  • 由于Apply和这些类具有耦合性,这些类的改变也带动了Apply内部的改变。但是如果我使用了接口,Apply中的代码完全不需要修改,外部不管怎么变,我自岿然不动,这就是解耦。

实现多个接口

  • 在Java中只能继承单个类,但是可以实现多个接口,将所有的接口名都置于implements关键字之后,用逗号隔开,并且可以向上转型为每个接口,因为每一个接口都是一个独立类型。
  • 如代码所示,一个超级英雄要具备打坏蛋,能上天也能入海的能力,可以实现多个接口,将这些能组合在一起,并且可以依次向上转型为任意一个接口被调用。
interface CanFight {
  void fight();
}

interface CanSwim {
  void swim();
}

interface CanFly {
  void fly();
}

class SuperHero implements CanFight, CanSwim, CanFly {
  public void swim() {}
  public void fly() {}
  public void fight(){}
}

public class Adventure {
  public static void t(CanFight x) { x.fight(); }
  public static void u(CanSwim x) { x.swim(); }
  public static void v(CanFly x) { x.fly(); }
  public static void w(ActionCharacter x) { x.fight(); }
  public static void main(String[] args) {
    Hero h = new Hero();
    t(h); // Treat it as a CanFight
    u(h); // Treat it as a CanSwim
    v(h); // Treat it as a CanFly
    w(h); // Treat it as an ActionCharacter
  }
}

类和接口

  • 类有属性,有行为,可以是对行为具体实现,接口只能对行为声明,无法具体实现,没有属性。
  • 类对事物的抽象,或者说是某个群体的抽象,比如人类、鸟类、飞机类等等。类有属性、有行为。继承一个类表示属于这个群体,是一种是不是的关系,男人是人,喜鹊是鸟。他们具有某些相同的能力和属性,比如大部分鸟都会飞,都有翅膀,有羽毛。
  • 接口是对行为的抽象,比如飞机和鸟都具备飞行能力,可以抽象出一个飞行的行为接口。实现接口表示具备这个行为,一种有没有或者具备不具备的关系,鸟具备飞的能力,飞机具备飞的能力。
  • 为什么类只有单继承,现实生活中很少有事物是属于同一层次的不同种类,即使有也会被单独划分为一个类,比如骡,它虽然是马和驴的杂交,但没有说驴既是马类也是驴类,而是被单独抽象成骡类。还有注意是同一层次,你说小明既是男人,也是人。从继承角度来说,男人肯定是继承自人,这样就不是同一层次了,这么说才是同一层次小明既是男人,也是女人,一听显然逻辑不对。
  • 为什么接口可以多种实现,因为事物可以具备多种行为,超级英雄既能游泳,也能跑步,还能打架,都可以单独抽象出各自的行为接口。
  • 从上面看接口的抽象程度高于类,那是不是我们都用实现接口的方式而不用继承呢。其实不然,类可以实现具体的行为,继承基类可以理解成一种模板设计,可以把相同的行为实现写在基类中,比如下面的ManWomen吃的行为逻辑一样,那么就直接在基类中实现逻辑,ManWomen继承即可。想一下如果不用继承,那ManWoman中都要实现相同的行为逻辑,写了一样的代码。
public class People implements Eat {
    @Override
    public void eat() {
        System.out.println("I can eat rice");
    }
}

public class Woman extends People {
}

public class Man extends People {
}

interface Eat {
    void eat();
}

通过继承扩展接口

接口之间也可以继承,并且可以多继承,也就是说一个接口可以继承多个其他接口,扩展原来的接口。比如原来已经有SwimFight接口,这时候我还想增加一个Help接口也具备游泳和打坏蛋的行为,就可以继承上面两个接口,当SuperHero实现Help接口的时候就需要实现上述3个方法。

interface Swim {
    void canSwim();
}

interface Fight {
    void canFight();
}

interface Help extends Swim, Fight {
    void canHelp();
}

public class SuperHero implements  Help {
    @Override
    public void canSwim() {

    }

    @Override
    public void canFight() {

    }

    @Override
    public void canHelp() {

    }
}

接口与工厂模式

  • 上面已经说了接口是把声明和实现分开,在工厂模式中这是需要这样的思想,不同工厂可以生产出对应产品,产品的功能因为设计图也会不一样。如代码所示一个汽车因为设计的不同会导致不同的速度,汽车生产商也会生产出不同的汽车。
interface Car {
    void running();
}

class Jeep implements Car {
    @Override
    public void running() {
        System.out.println("Jeep's speed is 80km/h");
    }
}

class Track implements Car {
    @Override
    public void running() {
        System.out.println("Track's speed is 60km/h");
    }
}

interface CarFactory {
    Car getCar();
}

class JeepFactory implements CarFactory {
    @Override
    public Car getCar() {
        return new Jeep();
    }
}

class TrackFactory implements CarFactory {
    @Override
    public Car getCar() {
        return new Track();
    }
}

public class User {
    private void drive(CarFactory carFactory) {
        carFactory.getCar().running();
    }
}

你可能感兴趣的:(Java编程思想读书笔记第九章:接口)