读书笔记 | 《Think in Java》Ⅸ 接口

214 阅读6分钟

信仰牌咖啡

Ⅸ 接口

9.1抽象类和抽象方法

抽象类是为了建立一种基本形式,以此表示所有导出类的公共部分。创建一个抽象类的对象是没有意义的,为了阻止使用者这么做,编译器会报错,以此保证抽象类的纯粹性。请记住,创建抽象类是希望通过这个通用接口操作一系列类。

包含一个或多个抽象方法的是抽象类,但是抽象类也可以包含非抽象方法。

如果一个类继承自一个抽象类,并想要创建其对象,那么必须实现基类中所有的抽象方法,否则,导出类还是一个抽象类,无法实例化对象,并且编译器会提醒我们使用abstract修饰。

9.2 接口

interface关键字使抽象的概念迈进了一步,interface关键字产生一个完全抽象的类,这个类不会提供任何具体实现。interface不仅仅是一个极度抽象的类,因为它允许人们通过创建一个能够被向上转型为多种基类的类型(实现多个接口),来实现某种类似于多继承的特性。

接口也可以包含域,并且这些域隐式地是finalstatic,这些域以前是被用作常量,后来当枚举类型出现后,这种方式已经弃而不用了,只需要了解就行了,以放在维护某些祖传代码时遇到。

9.3 完全解耦

假如一个方法的参数是一个A类对象的引用,而非一个接口实现类引用,那么你只能操作这个A类或者其子类。如果你想要将这个方法应用于不在A类继承结构中的某个类,那将寸步难行。接口很大程度上放宽了这种限制,使得代码的复用性更高。

举个栗子,假如有一个Processor类,有一个name方法,还有一个process方法。它的子类对process方法进行重写:

//Processor.java
public class Processor {
    public String name(){
        return getClass().getSimpleName();
    }

    Object process(Object input){
        return input;
    }
}

//Upcase.java
public class Upcase extends Processor {
    String process(Object input){
        return ((String)input).toUpperCase();
    }
}

//Downcase.java
public class Downcase extends Processor {
    String process(Object input){
        return ((String)input).toLowerCase();
    }
}

现在有一个方法要对Processor类型进行操作:

public class Apply {
    public static void process (Processor p,Object s){
        System.out.println(p.name());
        System.out.println(p.process(s));
    }
    
   public static String s="Welcome to Guohe";
            
    public static void main(String[] args){
        process(new Upcase(),s);
        process(new Downcase(),s);
    }
}

运行结果如图:

Apply.process()可以接受任何Processor类型。如同举的例子,创建一个能够根据所传递的参数对象不同而具有不同的行为的方法,被称为策略设计模式

现在问题来了,现在假如有一个T类,它也适用于这个Apply.process()方法,但是这个T类又不属于Processor继承结构中,所以无法复用Apply.process()代码。这是因为ProcessorApply.process()耦合度过高,已经超出了所需要的程度。

此时,若将Processor改造为一个接口,这些限制将会松动。改造如下:

public interface Processor {
    public String name(){}

    Object process(Object input){}
}
public class Apply {
    public static void process (Processor p,Object s){
        System.out.println(p.name());
        System.out.println(p.process(s));
    }

    public static String s="Welcome to Guohe";
            
    public static void main(String[] args){
        process(new Upcase(),s);
        process(new Downcase(),s);
        process(new Temple(),s);
        
    }

第一种方式:遵循接口来编写新的可继承的类(StringProcessor,TProcessor):

//StringProcessor.java
public class abstract StringProcessor implements Processor {
    public String name(){
        return getClass().getSimpleName();
    }
    String process(Object input){
        return input;
    }
}

//TProcessor.java
public class abstract TProcessor implements Processor {
    public String name(){
        return getClass().getSimpleName();
    }
    String process(Object input){
        //do something else
    }
}

//Upcase.java
public class Upcase extends StringProcessor {
    String process(Object input){
        return ((String)input).toUpperCase();
    }
}

//Downcase.java
public class Downcase extends StringProcessor {
    String process(Object input){
        return ((String)input).toLowerCase();
    }
}

//Temple.java
public class Temple extends TProcessor {
    String process(Object input){
       //do something else
    }
}

这将隐藏着一个弊端,因为在开发中,我们遇到的一些已经封装好的类我们是无法修改的。(上面的代码中,我们实际上是将T类直接改造成了TProcessor类,使其实现了Processor接口的内容,在对于Apply.prcess(Processor p,Object s)时,可以完成向上转型)。

很多现实情况是,不会允许我们修改T类。此时可以用到适配器设计模式,适配器将接受现有的接口,并产生你所需要的接口。构造一个T的适配器:

class TAdapter implements Processor{
  T t;
  public TAdapter(T t){
     this.t=t;
  }
  public String name(){
        return getClass().getSimpleName();
    }
  String process(Object input){
        //do something else
    }
}

此时,可以这么写:

Apply.process(new TAdapter(new T()),s);

9.4 Java中的多重继承

在C++中,组合多个类的接口的行为被称为多重继承,这会给你的类背上沉重的包袱,因为每个类都有具体实现。在Java中,只能继承一个父类,若要获得更多能力,可以实现多个接口。而接口本身是没有任何具体实现的——也就是说,没有任何与接口相关的存储。(请注意,这段话中,“接口”在不同语境下有着不同的含义,请勿混淆)

使用接口的核心原因:

  1. 为了能向上转型为多个基类型(这点将带来非常大的灵活性)
  2. 防止客户端程序员创建该类的对象。

9.5 通过继承来拓展接口

通过接口之间的继承,可以很容易地在接口中添加新的方法声明。还可以在新接口中组合数个接口。请注意,接口之间的继承还是使用extends关键词,而非implements

举个栗子:

interface A {
  void hello();
}

interface B {
  void hello1();
}

interface C extends A,B{
  void hello3();
}

class G implements C{
  void hello(){
    // do somrthing
  }
void hello2(){
    // do somrthing
  }
void hello3(){
    // do somrthing
  }
}

9.7 接口中的域

前文提到:放入接口中的任何域都是隐式的staticfinal,所以接口就成为一种很便捷的用来创建常量组的工具。当然,在JDK5中enum出现后,原来的这种方式已经弃而不用了。

9.9 接口与工厂

接口是实现多重继承的途径,而生成遵循某个接口的对象的典型方式就是工厂设计模式。这与直接调用构造器不同,在工厂对象上调用创建方法来创建接口的某个实现的对象。理论上,通过这种方式,我们的代码将完全与接口的实现分离。

//服务接口
interface Service{
  void method1();
  void method2();
}
//工厂接口
interface ServiceFactory{
  Service getService();
}
class Impl implements Service{
  Impl(){}
  public void method1(){
    print("method1 in Impl");
  }
  public void method2(){
    print("method2 in Impl");
  }
}
class ImplFactory implements ServiceFactory{
  public Service getService(){
    return new Impl();
  }
}

class Impl2 implements Service{
  Impl(){}
  public void method1(){
    print("method1 in Impl2");
  }
  public void method2(){
    print("method2 in Impl2");
  }
}
class Impl2Factory implements ServiceFactory{
  public Service getService(){
    return new Impl2();
  }
}

假如没有工厂,你在想要使用接口的具体某个实现时,必须在业务代码的某处指明。在这个模式中,服务与工厂一一对应。那为什么需要这种额外的间接?

  • 优点:
  1. 利用工厂的工厂方法类去创建具体的产品对象,隐藏了具体产品对象的创建细节,只需要关心具体产品对应的具体工厂; 2.遵守开闭原则。加入新的产品类时,只需要同时加入工厂类就可以实现扩展,无需修改原来的代码。
  • 缺点:
  1. 随着产品种类的数量的增长,工厂类也会随之增加,将不利于系统的维护,增加系统编译和运行的开销。

扫一扫,关注公众号

小白的成长探索之路。