第九章 接口(上)
接口和内部类为我们提供了一种将接口与实现分离的更加结构化的方法。
9.1 抽象类和抽象方法
包含抽象方法的类叫做抽象类。如果一个类包含一个或多个抽象方法,该类必须被限定为抽象的。(否则,编译器就会报错。)
抽象类和抽象方法都用abstract修饰,例如:abstract void f(); abstract class Instrument{}
第八章Instrument类可以很容易地转化成abstract类。既然使某个类称为抽象类并不需要所有的方法都是抽象的,所以仅需将某些方法声明为抽象的即可。如下图所示:
下面是修改过的“管弦乐器”的例子,其中采用了抽象类和抽象方法:
abstract class Instrument{
private int i;
public abstract void play(Note n);
public String what(){
return "Instrument";
}
public abstract void adjust();
}
class Wind extends Instrument{
public void play(Note n){
System.out.println("Wind.play"+n);
}
public void adjust(){}
public String what(){
return "Wind";
}
}
class Percussion extends Instrument{
public void play(Note n) {
System.out.println("Percussion.play"+n);
}
public void adjust() {}
public String what(){
return "Percussion";
}
}
class Stringed extends Instrument{
@Override
public void play(Note n) {
System.out.println("Stringed.play"+n);
}
@Override
public void adjust() {}
public String what(){
return "Stringed";
}
}
class Brass extends Wind{
public void play(Note n){
System.out.println("Brass.play"+n);
}
public void adjust(){
System.out.println("Brass.adjust");
}
}
class Woodwind extends Wind{
public void play(Note n){
System.out.println("Woodwind.play"+n);
}
public String what(){
return "Woodwind";
}
}
public class Music4 {
static void tune(Instrument i){
i.play(Note.MIDDLE_C);
}
static void tuneAll(Instrument [] e){
for(Instrument i:e){
tune(i);
}
}
public static void main(String[] args) {
Instrument [] orchestra={
new Wind(),
new Percussion(),
new Stringed(),
new Brass(),
new Woodwind()
};
tuneAll(orchestra);
public abstract void play(Note n);
public String what(){
return "Instrument";
}
public abstract void adjust();
}
class Wind extends Instrument{
public void play(Note n){
System.out.println("Wind.play"+n);
}
public void adjust(){}
public String what(){
return "Wind";
}
}
class Percussion extends Instrument{
public void play(Note n) {
System.out.println("Percussion.play"+n);
}
public void adjust() {}
public String what(){
return "Percussion";
}
}
class Stringed extends Instrument{
@Override
public void play(Note n) {
System.out.println("Stringed.play"+n);
}
@Override
public void adjust() {}
public String what(){
return "Stringed";
}
}
class Brass extends Wind{
public void play(Note n){
System.out.println("Brass.play"+n);
}
public void adjust(){
System.out.println("Brass.adjust");
}
}
class Woodwind extends Wind{
public void play(Note n){
System.out.println("Woodwind.play"+n);
}
public String what(){
return "Woodwind";
}
}
public class Music4 {
static void tune(Instrument i){
i.play(Note.MIDDLE_C);
}
static void tuneAll(Instrument [] e){
for(Instrument i:e){
tune(i);
}
}
public static void main(String[] args) {
Instrument [] orchestra={
new Wind(),
new Percussion(),
new Stringed(),
new Brass(),
new Woodwind()
};
tuneAll(orchestra);
}
}
运行结果:
Wind.playMIDDLE_C
Percussion.playMIDDLE_C
Stringed.playMIDDLE_C
Brass.playMIDDLE_C
Percussion.playMIDDLE_C
Stringed.playMIDDLE_C
Brass.playMIDDLE_C
Woodwind.playMIDDLE_C
我们可以看出,除了基类,实际上并没有什么改变。
创建抽象类和抽象方法非常有用,因为它们可以使类的抽象性明确起来,并告诉用户和编译器打算怎样来使用它们。抽象类还是很有用的重构工具,因为它们使得我们可以很容易地将公共方法沿着继承层次结构向上移动。
9.2 接口
interface关键字使抽象类的概念更向前迈进了一步。abstract关键字允许人们在类中创建一个或多个没有任何定义的方法——提供了接口部分,但是没有提供任何相应的具体实现,这些实现是由此类的继承者创建的。interface这个关键字产生一个完全抽象的类,它根本就没有提供任何具体实现。它允许创建者确定方法名、参数列表和返回类型,但是没有任何方法体。接口只提供了形式,而未提供任何具体实现。
一个接口表示:“所有实现了该特定接口的类看起来都像这样”。因此,任何使用某特定接口的代码都知道可以调用该接口的哪些方法,而且仅需知道这些。故接口被用来建立类与类之间的协议。(某些面向对象编程语言使用关键字protocol来完成这一功能。)
但是,interface不仅仅是一个极度抽象的类,因为它允许人们通过创建一个能够被向上转型为多种基类的类型,来实现某种类似多重继承变种的特性。
要想创建一个接口,需要用interface关键字来替代class关键字。就像类一样,可以在interface关键字前面添加public关键字(但仅限于该接口在与其同名的文件中被定义)。如果不添加public关键字,则它只是具有包访问权限,这样它就只能在同一个包内可用。接口也可以包含域,但是这些域隐式地是static和final的。
要让一个类遵循某个特定接口(或者是一组接口),需要使用implements关键字,它表示:“interface只是它的外貌,但是现在我要声明它是如何工作的。”除此之外,它看起来还很像继承。“乐器”示例的图说明了这一点:
可以从Woodwind和Brass类中看到,一旦实现了某个接口,其实现就变成了一个普通的类,就可以按常规方式扩展它。
可以选择在接口中显式地将方法声明为public的,但即使你不这么做,它们也是public的。因此,当要实现一个接口时,在接口中被定义的方法必须被定义为是public的,否则,他们将只能得到默认的包访问权限,这样在方法被继承的过程中,其可访问权限就被降低了,这是Java编译器所不允许的。
interface Instrument{
int VALUE=5;
void play(Note n);
void adjust();
}
class Wind implements Instrument{
public void play(Note n){
System.out.println(this+".play()"+n);
}
@Override
public void adjust() {
System.out.println(this+".adjust()");
}
public String toString(){
return "Wind";
}
}
class Percussion implements Instrument{
@Override
public void play(Note n) {
System.out.println(this+".play()"+n);
}
@Override
public void adjust() {
System.out.println(this+".adjust()");
}
public String toString(){
return "Percussion";
}
}
class Stringed implements Instrument{
@Override
public void play(Note n) {
System.out.println(this+".play()"+n);
}
@Override
public void adjust() {
System.out.println(this+".adjust()");
}
public String toString(){
return "Stringed";
}
}
class Brass extends Wind{
public String toString(){
return "Brass";
}
}
class Woodwind extends Wind{
public String toString(){
return "Woodwind";
}
}
public class Music5 {
static void tune(Instrument i){
i.play(Note.MIDDLE_C);
}
static void tuneAll(Instrument [] e){
for(Instrument i:e){
tune(i);
}
}
public static void main(String[] args) {
Instrument [] orchaestra={
new Wind(),
new Percussion(),
new Stringed(),
new Brass(),
new Woodwind()
};
tuneAll(orchaestra);
}
void play(Note n);
void adjust();
}
class Wind implements Instrument{
public void play(Note n){
System.out.println(this+".play()"+n);
}
@Override
public void adjust() {
System.out.println(this+".adjust()");
}
public String toString(){
return "Wind";
}
}
class Percussion implements Instrument{
@Override
public void play(Note n) {
System.out.println(this+".play()"+n);
}
@Override
public void adjust() {
System.out.println(this+".adjust()");
}
public String toString(){
return "Percussion";
}
}
class Stringed implements Instrument{
@Override
public void play(Note n) {
System.out.println(this+".play()"+n);
}
@Override
public void adjust() {
System.out.println(this+".adjust()");
}
public String toString(){
return "Stringed";
}
}
class Brass extends Wind{
public String toString(){
return "Brass";
}
}
class Woodwind extends Wind{
public String toString(){
return "Woodwind";
}
}
public class Music5 {
static void tune(Instrument i){
i.play(Note.MIDDLE_C);
}
static void tuneAll(Instrument [] e){
for(Instrument i:e){
tune(i);
}
}
public static void main(String[] args) {
Instrument [] orchaestra={
new Wind(),
new Percussion(),
new Stringed(),
new Brass(),
new Woodwind()
};
tuneAll(orchaestra);
}
}
运行结果:
Wind.play()MIDDLE_C
Percussion.play()MIDDLE_C
Stringed.play()MIDDLE_C
Brass.play()MIDDLE_C
Percussion.play()MIDDLE_C
Stringed.play()MIDDLE_C
Brass.play()MIDDLE_C
Woodwind.play()MIDDLE_C
此实例的这个版本还有另外一处改动:what()方法已经被修改为toString()方法,因为toString()的逻辑正是what()要实现的逻辑。由于toString()方法是根据类Object的一部分,因此它不需要出现在接口中。余下的代码其工作方式都是相同的。无论是将其向上转型为称为Instrument的普通类,还是称为Instrument的抽象类,或是称为Instrument的接口,都不会有问题。它的行为都是相同的。事实上,你可以在tune()方法中看到,没有任何依据来证明Instrument是一个普通类、抽象类,还是一个接口。
9.3 完全解耦
只要一个方法操作的是类而非接口,那么你就只能使用这个类及其子类。如果你想要将这个方法应用于不在此继承结构中的某个类,那么你就会触霉头了。接口可以在很大程度上放宽这种限制,因此,它使得我们可以编写可复用性更好的代码。
例如,假设有一个Processor类,它有一个name()方法;另外还有一个process()方法,该方法接受输入参数,修改它的值,然后产生输出。这个类做为基类而被扩展,用来创建个中不同类型的Processor。在本例中,Processor的子类将修改String对象(注意,返回类型可以是协变类型,而非参数类型)
class Processor{
public String name(){
return getClass().getSimpleName();
}
Object process(Object input){
return input;
}
}
class Upcase extends Processor{
String process(Object input){
return ((String) input).toUpperCase();
}
}
class Downcase extends Processor{
String process(Object input){
return ((String) input).toLowerCase();
}
}
class Splitter extends Processor{
String process(Object input){
return Arrays.toString(((String) input).split(" "));
}
}
public class Apply {
public static void process(Processor p,Object s){
System.out.println("Using Process "+p.name());
System.out.println(p.process(s));
}
public static String s="Disagereement with beliefs is by definition incorrect";
public static void main(String[] args) {
process(new Upcase(),s);
process(new Downcase(),s);
process(new Splitter(),s);
}
return getClass().getSimpleName();
}
Object process(Object input){
return input;
}
}
class Upcase extends Processor{
String process(Object input){
return ((String) input).toUpperCase();
}
}
class Downcase extends Processor{
String process(Object input){
return ((String) input).toLowerCase();
}
}
class Splitter extends Processor{
String process(Object input){
return Arrays.toString(((String) input).split(" "));
}
}
public class Apply {
public static void process(Processor p,Object s){
System.out.println("Using Process "+p.name());
System.out.println(p.process(s));
}
public static String s="Disagereement with beliefs is by definition incorrect";
public static void main(String[] args) {
process(new Upcase(),s);
process(new Downcase(),s);
process(new Splitter(),s);
}
}
运行结果:
Using Process Upcase
DISAGEREEMENT WITH BELIEFS IS BY DEFINITION INCORRECT
Using Process Downcase
disagereement with beliefs is by definition incorrect
Using Process Splitter
Using Process Downcase
disagereement with beliefs is by definition incorrect
Using Process Splitter
[Disagereement, with, beliefs, is, by, definition, incorrect]
Apply.process()方法可以接受任何类型的Processor,并将其应用到一个Object对象上,然后打印结果。像本例这样,创建一个能够根据所传递的参数对象的不同而具有不同行为的方法称为策略设计模式。这类方法包含所要执行的算法中固定不变的部分,而“策略”包含变化的部分。策略就是传递进去的参数对象,它包含要执行的代码。这里,Processor对象就是一个策略,在main()中可以看到有三种不同类型的策略应用到了String类型的对象上。
split()方法是String类的一部分,它接受String类型的对象,并以传递进来的参数作为边界,将该String对象隔开,然后返回一个数组String[]。
现在假设我们发现了一组电子滤波器,它们看起来好像适用于Apply.process()方法:
public class Waveform {
private static long counter;
private final long id=counter++;
public String toString(){
return "Waveform"+id;
}
private final long id=counter++;
public String toString(){
return "Waveform"+id;
}
}
public class Filter {
public String name(){
return getClass().getSimpleName();
}
public String name(){
return getClass().getSimpleName();
}
}
public class LowPass extends Filter{
double cutoff;
public LowPass(double cutoff){
this.cutoff=cutoff;
}
public Waveform process(Waveform input){
return input;
}
double cutoff;
public LowPass(double cutoff){
this.cutoff=cutoff;
}
public Waveform process(Waveform input){
return input;
}
}
public class HighPass extends Filter {
double cutoff;
public HighPass(double cutoff){
this.cutoff=cutoff;
}
public Waveform process(Waveform input){
return input;
}
double cutoff;
public HighPass(double cutoff){
this.cutoff=cutoff;
}
public Waveform process(Waveform input){
return input;
}
}
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;
}
double lowCutoff,highCutoff;
public BandPass(double lowCut,double highCut){
lowCutoff=lowCut;
highCutoff=highCut;
}
public Waveform process(Waveform input){
return input;
}
}
Filter与Processor具有相同的接口元素,但是因为它并非继承自Process——因为Filter类的创建者压根不清楚你想要将它用作Process——因此你不能将Filter用于Apply.process()方法,即便这样做可以正常运行。这里主要是因为Apply.process()方法和Process之间的耦合过紧,已经超出了所需要的程度,这样就使得应该复用Apply.process()的代码时,复用却被禁止了。另外还需要注意的是它们的输入和输出都是Waveform。
但是,如果Process是一个接口,那么这些限制就会变得松动,使得你可以复用结构该接口的Apply.process()。下面是Processor和Apply的修改版本:
public interface Processor {
String name();
Object process(Object input);
Object process(Object input);
}
public class Apply {
public static void process(Processor p,Object s){
System.out.println("Using Process "+p.name());
System.out.println(p.process(s));
System.out.println("Using Process "+p.name());
System.out.println(p.process(s));
}
复用代码的第一种方式是客户端程序员遵循该接口来编写他们自己的类,就像下面这样:
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){
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(" "));
}
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){
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(" "));
}
}
运行结果:
Using Process Upcase
IF SHE WEIGHS THE SAME AS A DUCK ,SHE'S MADE OF WOOD
Using Process Downcase
if she weighs the same as a duck ,she's made of wood
Using Process Splitter
IF SHE WEIGHS THE SAME AS A DUCK ,SHE'S MADE OF WOOD
Using Process Downcase
if she weighs the same as a duck ,she's made of wood
Using Process Splitter
[If, she, weighs, the, same, as, a, duck, ,she's, made, of, wood]
但是,你经常碰到的情况是你无法修改你想要使用的类。例如,在电子滤波器的例子中,类库是被发现而非创建的。在这些情况下,可以使用适配器设计模式。适配器中的代码将接受你所拥有的接口,并产生你所需要的接口,就像下面这样:
class FilterAdapter implements Processor{
Filter filter;
public FilterAdapter(Filter filter){
this.filter=filter;
}
@Override
public String name() {
return filter.name();
}
@Override
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);
}
public FilterAdapter(Filter filter){
this.filter=filter;
}
@Override
public String name() {
return filter.name();
}
@Override
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);
}
}
运行结果:
Using Process LowPass
Waveform0
Using Process HighPass
Waveform0
Using Process BandPass
Using Process HighPass
Waveform0
Using Process BandPass
Waveform0
在这种使用适配器的方式中,FilterAdapter的构造器接受你所拥有的接口Filter,然后生成具有你所需要的Processor接口的对象。你可能还注意到了,在FilterAdapter类中用到了代理。
将接口从具体实现中解耦使得接口可以应用于多种不同的具体实现,因此代码也就更具可复用性。