接口#
抽象类和抽象方法#
抽象方法仅有声明,没有方法体,如 abstract void f();
。
包含抽象方法的类必须限定为抽象类,如
abstract class ClassName {}
,我们创建抽象类的目的是希望通过这个通用接口操纵一系列类。
如果从一个抽象类继承,并想创建该新类的对象,那么就必须为基类中的所有抽象方法提供方法定义。
如果不这样做(可以选择不做),那么导出类便也是抽象类,且编译器将会强制我们使用 abstract
关键字来限定这个类。
接口定义#
抽象方法可以提供接口,抽象方法所属的类是 部分抽象 的类(因为抽象类中可以有某些方法的实现)。但是
interface
关键字可以产生一个 完全抽象 的类,如 interface ClassName {}
。
接口和类一样,也有访问权限控制,如果你想让这个接口具有全局访问性,那么用 public
声明它,否则它只有包访问权限。
在接口中声明的方法,若不加以声明,默认都是 public
访问权限。
在接口中声明的内部类,若不加以声明,默认都是 public static
的。
在接口中声明的属性,若不加以声明,默认都是 public static final
的。
接口被用来建立类与类之间的 协议。
要让某个类遵循某个特定协议(或者一组协议),implements
它即可。
使用接口的核心原因是为了能够向上转型为多个基类型(以及由此带来的灵活性)。 事实上,如果知道某个事物应该成为一个基类,那么第一选择应该是使它成为一个接口。 接口是一种重要的工具,但是它们容易被滥用,恰当的原则应该是优先选择类而不是接口。
任何抽象性都应该是应真正的需求而产生的。 当必需时,你应该重构接口而不是到处添加额外级别的间接性,而这又会带来额外的复杂性。
接口和内部类为我们提供了一种将 接口与实现分离 的更加结构化的方法。
完全解耦#
当一个系统足够大时,解耦应该是一个必然会聊到的话题,而选择不同的实现方式,解耦能力也不同。 如果使用继承,则无法完全解耦,而使用接口,则可以实现完全解耦。
由于单根继承结构的局限性,在使用继承语法的实现中,若想要利用多态的特性,我们只能调用基类中的方法。 因此,基类和导出类之间是一对多的树形关系。
而在使用接口语法的实现中,摆脱了单根继承结构的局限性,赋予了 Java “多重继承” 的能力。 一个类可能实现了多个接口,那么我们调用任何一个接口中的任何一个方法,都可以动态绑定到实现类上。 因此,接口类和实现类之间是多对多的网状关系,大大扩展了解耦和扩展能力。
语言表述总是难以给人一个很直观的印象。若我们有一个符合如下继承关系的几个类:
Apply.java
//: interfaces/classprocessor/Apply.java
package interfaces.classprocessor;
import java.util.*;
import static net.mindview.util.Print.*;
class Processor {
public String name() {
return getClass().getSimpleName();
}
Object process(Object input) { return input; }
}
class Upcase extends Processor {
String process(Object input) { // Covariant return
return ((String)input).toUpperCase();
}
}
class Downcase extends Processor {
String process(Object input) {
return ((String)input).toLowerCase();
}
}
class Splitter extends Processor {
String process(Object input) {
// The split() argument divides a String into pieces:
return Arrays.toString(((String)input).split(" "));
}
}
public class Apply {
public static void process(Processor p, Object s) {
print("Using Processor " + p.name());
print(p.process(s));
}
public static String s =
"Disagreement with beliefs is by definition incorrect";
public static void main(String[] args) {
process(new Upcase(), s);
process(new Downcase(), s);
process(new Splitter(), s);
}
} /* Output:
Using Processor Upcase
DISAGREEMENT WITH BELIEFS IS BY DEFINITION INCORRECT
Using Processor Downcase
disagreement with beliefs is by definition incorrect
Using Processor Splitter
[Disagreement, with, beliefs, is, by, definition, incorrect]
*///:~
Filter.java
//: interfaces/filters/Waveform.java
package interfaces.filters;
public class Waveform {
private static long counter;
private final long id = counter++;
public String toString() { return "Waveform " + id; }
} ///:~
//: interfaces/filters/Filter.java
package interfaces.filters;
public class Filter {
public String name() {
return getClass().getSimpleName();
}
public Waveform process(Waveform input) { return input; }
} ///:~
//: interfaces/filters/LowPass.java
package interfaces.filters;
public class LowPass extends Filter {
double cutoff;
public LowPass(double cutoff) { this.cutoff = cutoff; }
public Waveform process(Waveform input) {
return input; // Dummy processing
}
} ///:~
//: interfaces/filters/HighPass.java
package interfaces.filters;
public class HighPass extends Filter {
double cutoff;
public HighPass(double cutoff) { this.cutoff = cutoff; }
public Waveform process(Waveform input) { return input; }
} ///:~
//: interfaces/filters/BandPass.java
package interfaces.filters;
public class BandPass extends Filter {
double lowCutoff, highCutoff;
public BandPass(double lowCut, double highCut) {
lowCutoff = lowCut;
highCutoff = highCut;
}
public Waveform process(Waveform input) { return input; }
} ///:~
Filter
与 Processor
具有相同的接口元素 process()
,但是因为 Filter
并非继承自
Processor
,因此在调用 Apply.process()
时,并不会触发 Filter
类的 process()
方法。
这主要是因为 Apply.process()
和 Processor.process()
之间的 耦合过紧,于是将其应用于 Filter
时,复用被禁止 了。
但是,如果 Processor
是一个接口(之前是一个普通的类),这些限制就会变得松动,就可以实现复用了。
接口方式实现
//: interfaces/interfaceprocessor/Processor.java
package interfaces.interfaceprocessor;
public interface Processor {
String name();
Object process(Object input);
} ///:~
//: interfaces/interfaceprocessor/Apply.java
package interfaces.interfaceprocessor;
import static net.mindview.util.Print.*;
public class Apply {
public static void process(Processor p, Object s) {
print("Using Processor " + p.name());
print(p.process(s));
}
} ///:~
//: interfaces/interfaceprocessor/StringProcessor.java
package interfaces.interfaceprocessor;
import java.util.*;
public abstract class StringProcessor implements Processor{
public String name() {
return getClass().getSimpleName();
}
public abstract String process(Object input);
public static String s =
"If she weighs the same as a duck, she's made of wood";
public static void main(String[] args) {
Apply.process(new Upcase(), s);
Apply.process(new Downcase(), s);
Apply.process(new Splitter(), s);
}
}
class Upcase extends StringProcessor {
public String process(Object input) { // Covariant return
return ((String)input).toUpperCase();
}
}
class Downcase extends StringProcessor {
public String process(Object input) {
return ((String)input).toLowerCase();
}
}
class Splitter extends StringProcessor {
public String process(Object input) {
return Arrays.toString(((String)input).split(" "));
}
} /* Output:
Using Processor Upcase
IF SHE WEIGHS THE SAME AS A DUCK, SHE'S MADE OF WOOD
Using Processor Downcase
if she weighs the same as a duck, she's made of wood
Using Processor Splitter
[If, she, weighs, the, same, as, a, duck,, she's, made, of, wood]
*///:~
//: interfaces/interfaceprocessor/FilterProcessor.java
package interfaces.interfaceprocessor;
import interfaces.filters.*;
class FilterAdapter implements Processor {
Filter filter;
public FilterAdapter(Filter filter) {
this.filter = filter;
}
public String name() { return filter.name(); }
public Waveform process(Object input) {
return filter.process((Waveform)input);
}
}
public class FilterProcessor {
public static void main(String[] args) {
Waveform w = new Waveform();
Apply.process(new FilterAdapter(new LowPass(1.0)), w);
Apply.process(new FilterAdapter(new HighPass(2.0)), w);
Apply.process(
new FilterAdapter(new BandPass(3.0, 4.0)), w);
}
} /* Output:
Using Processor LowPass
Waveform 0
Using Processor HighPass
Waveform 0
Using Processor BandPass
Waveform 0
*///:~
高亮除的代码实现了 适配器模式。 适配器模式的出现是因为无法修改旧接口,而且旧接口无法与满足新业务的接口需求。 引进适配器后,在旧接口的基础上稍作修改,满足新业务。
在适配器方式中,FilterAdapter
的构造器接受你已经拥有的接口
Filter
,然后生成具有你所需要的 Processor
接口的对象。
按照之前的需求,我们还是想要用 Apply.process()
能够同时处理 Processor.process()
和
Filter.process()
。修改后的代码结构,既可以让 Processor
应用于 StringProcessor
也可以应用于 FilterAdapter
,而后者,是继承无法办到的。这实现了 Processor.process()
与
StringProcessor.process()
的解耦。
从这个解决方案可以看出,没有什么是加一层是不能解决的。
Apply.process()
方法接受 Processor
类型的对象,并不接受 Filter
类型的对象,那么我们将
Processor
作为接口,FilterAdapter
也来实现这个接口,然后由 FilterAdapter
接受
Filter
类型的对象,问题就解决了。
再举一个关于解耦的例子。
Scanner
类的构造器接受一个 Readable
接口。
Readable
是专门为 Scanner
类设计的接口。
Scanner
不必将参数限定为某个特定类,只要某个类实现了 Readable
接口就可以作为
Scanner
的参数,这就很好地体现了可扩展性和完全解耦。
//: interfaces/RandomWords.java
// Implementing an interface to conform to a method.
import java.nio.*;
import java.util.*;
public class RandomWords implements Readable {
private static Random rand = new Random(47);
private static final char[] capitals = "ABCDEFGHIJKLMNOPQRSTUVWXYZ".toCharArray();
private static final char[] lowers = "abcdefghijklmnopqrstuvwxyz".toCharArray();
private static final char[] vowels = "aeiou".toCharArray();
private int count;
public RandomWords(int count) { this.count = count; }
public int read(CharBuffer cb) { // 实现 Readable 接口需要重写 read() 方法
if(count-- == 0)
return -1; // Indicates end of input
cb.append(capitals[rand.nextInt(capitals.length)]);
for(int i = 0; i < 4; i++) {
cb.append(vowels[rand.nextInt(vowels.length)]);
cb.append(lowers[rand.nextInt(lowers.length)]);
}
cb.append(" ");
return 10; // Number of characters appended
}
public static void main(String[] args) {
Scanner s = new Scanner(new RandomWords(10));
while(s.hasNext())
System.out.println(s.next());
}
} /* Output:
Yazeruyac
Fowenucor
Goeazimom
Raeuuacio
Nuoadesiw
Hageaikux
Ruqicibui
Numasetih
Kuuuuozog
Waqizeyoy
*///:~
嵌套接口#
接口可以嵌套在类或其他接口中。需要注意的是,当实现某个接口时,不需要实现
嵌套在其内部的任何接口,而且 private
接口不能在定义它的类之外被实现。
//: interfaces/nesting/NestingInterfaces.java
package interfaces.nesting;
class A {
interface B {
void f();
}
public class BImp implements B {
public void f() {}
}
private class BImp2 implements B {
public void f() {}
}
public interface C {
void f();
}
class CImp implements C {
public void f() {}
}
private class CImp2 implements C {
public void f() {}
}
private interface D {
void f();
}
private class DImp implements D {
public void f() {}
}
public class DImp2 implements D {
public void f() {}
}
public D getD() { return new DImp2(); }
private D dRef;
public void receiveD(D d) {
dRef = d;
dRef.f();
}
}
interface E {
interface G {
void f();
}
// Redundant "public":
public interface H {
void f();
}
void g();
// Cannot be private within an interface:
//! private interface I {}
}
public class NestingInterfaces {
public class BImp implements A.B {
public void f() {}
}
class CImp implements A.C {
public void f() {}
}
// Cannot implement a private interface except
// within that interface's defining class:
//! class DImp implements A.D {
//! public void f() {}
//! }
class EImp implements E {
public void g() {}
}
class EGImp implements E.G {
public void f() {}
}
class EImp2 implements E {
public void g() {}
class EG implements E.G {
public void f() {}
}
}
public static void main(String[] args) {
A a = new A();
// Can't access A.D:
//! A.D ad = a.getD();
// Doesn't return anything but A.D:
//! A.DImp2 di2 = a.getD();
// Cannot access a member of the interface:
//! a.getD().f();
// Only another A can do anything with getD():
A a2 = new A();
a2.receiveD(a.getD());
}
} ///:~
接口与工厂模式#
工厂模式是利用工厂批量生产对象的方式。理论上,通过这种方式,我们的代码将完全与接口的实现分离。 这就使得我们可以透明地将某个实现替换为另一个实现。 另外一种更优雅的方式创建工厂是使用 匿名内部类。 创建这种额外的间接性一个常见的原因是想要创建框架。
为了更深刻地理解工厂模式,我们还是用一段代码来理解一下。
//: interfaces/Factories.java
import static net.mindview.util.Print.*;
interface Service {
void method1();
void method2();
}
interface ServiceFactory {
Service getService();
}
class Implementation1 implements Service {
Implementation1() {} // Package access
public void method1() {print("Implementation1 method1");}
public void method2() {print("Implementation1 method2");}
}
class Implementation1Factory implements ServiceFactory {
public Service getService() {
return new Implementation1();
}
}
class Implementation2 implements Service {
Implementation2() {} // Package access
public void method1() {print("Implementation2 method1");}
public void method2() {print("Implementation2 method2");}
}
class Implementation2Factory implements ServiceFactory {
public Service getService() {
return new Implementation2();
}
}
public class Factories {
public static void serviceConsumer(ServiceFactory fact) {
Service s = fact.getService();
s.method1();
s.method2();
}
public static void main(String[] args) {
serviceConsumer(new Implementation1Factory());
// Implementations are completely interchangeable:
serviceConsumer(new Implementation2Factory());
}
} /* Output:
Implementation1 method1
Implementation1 method2
Implementation2 method1
Implementation2 method2
*///:~