重温经典《Thinking in java》第四版之第九章 接口(四十七)

简介: 重温经典《Thinking in java》第四版之第九章 接口(四十七)

9.3 完全解耦

只要一个方法操作的是类而非接口,那么你就只能使用这个类及其子类。如果你想要将这个方法应用于不在此继承结构中的某个类,那么你就会触霉头了。接口可以很大程度上放宽这种限制,因此,它使得我们可以编写复用性更好的代码。

例如,假设有一个Processor的类,它有一个name()方法,另外还有一个process()方法,该方法接受输入参数,修改它的值,然后产生输出。这个类作为基类而被扩展,用来创建各种不同类型的Processor。在本例中,Processor的子类将修改String对象(注意,返回类型可以是协变类型,而非参数类型):

packageinterfaces.classprocessor; 
importjava.util.*; 
importstaticnet.mindview.util.Print.*; 
classProcessor { 
publicStringname() { 
returngetClass().getSimpleName(); 
    } 
Objectprocess(Objectinput) { returninput; } 
} 
classUpcaseextendsProcessor { 
Stringprocess(Objectinput) { // Covariant return return ((String)input).toUpperCase(); 
    } 
} 
classDowncaseextendsProcessor { 
Stringprocess(Objectinput) { 
return ((String)input).toLowerCase(); 
    } 
} 
classSplitterextendsProcessor { 
Stringprocess(Objectinput) { 
// The split() argument divides a String into pieces: returnArrays.toString(((String)input).split(" ")); 
    } 
} 
publicclassApply { 
publicstaticvoidprocess(Processorp, Objects) { 
print("Using Processor "+p.name()); 
print(p.process(s)); 
    } 
publicstaticStrings="Disagreement with beliefs is by definition incorrect"; 
publicstaticvoidmain(String[] args) { 
process(newUpcase(), s); 
process(newDowncase(), s); 
process(newSplitter(), 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]

*///:~

Apply.process()方法可以接受任何类型的Processor,并将其应用到一个Object对象上,然后打印结果。像本例这样,创建一个能够根据所传递的参数对象的不同而具有不同行为的方法,被称为策略设计模式。这类方法包含所要执行的算法中固定不变的部分,而“策略”包含变化的部分。策略就是传递进去的参数对象,它包含要执行的代码。这里,Processor对象就是一个策略,在main()中可以看到有三种不同的策略应用到了String类型的s对象上。

 

现在嘉禾我们发现了一组电子滤波器,它们看起来好像适用于Apply.process()方法:

//: interfaces/filters/Waveform.java packageinterfaces.filters; 
publicclassWaveform { 
privatestaticlongcounter; 
privatefinallongid=counter++; 
publicStringtoString() { return"Waveform "+id; } 
} ///:~ //: interfaces/filters/Filter.java packageinterfaces.filters; 
publicclassFilter { 
publicStringname() { 
returngetClass().getSimpleName(); 
    } 
publicWaveformprocess(Waveforminput) { returninput; } 
} ///:~ //: interfaces/filters/LowPass.java packageinterfaces.filters; 
publicclassLowPassextendsFilter { 
doublecutoff; 
publicLowPass(doublecutoff) { this.cutoff=cutoff; } 
publicWaveformprocess(Waveforminput) { 
returninput; // Dummy processing     } 
} ///:~ //: interfaces/filters/HighPass.java packageinterfaces.filters; 
publicclassHighPassextendsFilter { 
doublecutoff; 
publicHighPass(doublecutoff) { this.cutoff=cutoff; } 
publicWaveformprocess(Waveforminput) { returninput; } 
} ///:~ //: interfaces/filters/BandPass.java packageinterfaces.filters; 
publicclassBandPassextendsFilter { 
doublelowCutoff, highCutoff; 
publicBandPass(doublelowCut, doublehighCut) { 
lowCutoff=lowCut; 
highCutoff=highCut; 
    } 
publicWaveformprocess(Waveforminput) { returninput; } 
}

///:~

Filter与Processor具有相同的接口元素,但是它并非继承自Processor——因为Filter类的创建者压根不清楚你想要将它用作Processor——因此你不能将Filter用于Apply.process()方法,即便这样做可以正常运行。这里主要是因为Apply.process()方法和Processor之间的耦合过紧,已经超出了所需要的程度,这就使得应该复用Apply.process()的代码时,复用却被禁止了。另外还需要注意的是它们的输入和输出都是Waveform。

但是,如果Processor是一个接口,那么这些限制就会变得松动,使得你可以复用该接口的Apply.process()。下面是Processor和Apply的修改版本:

//: interfaces/interfaceprocessor/Processor.java packageinterfaces.interfaceprocessor; 
publicinterfaceProcessor { 
Stringname(); 
Objectprocess(Objectinput); 
} ///:~ //: interfaces/interfaceprocessor/Apply.java packageinterfaces.interfaceprocessor; 
importstaticnet.mindview.util.Print.*; 
publicclassApply { 
publicstaticvoidprocess(Processorp, Objects) { 
print("Using Processor "+p.name()); 
print(p.process(s)); 
    } 
}

///:~

复用代码的第一种方式是客户端程序员遵循该接口来编写他们自己的类,就像下面这样:

//: interfaces/interfaceprocessor/StringProcessor.java packageinterfaces.interfaceprocessor; 
importjava.util.*; 
publicabstractclassStringProcessorimplementsProcessor{ 
publicStringname() { 
returngetClass().getSimpleName(); 
    } 
publicabstractStringprocess(Objectinput); 
publicstaticStrings="If she weighs the same as a duck, she’s made of wood"; 
publicstaticvoidmain(String[] args) { 
Apply.process(newUpcase(), s); 
Apply.process(newDowncase(), s); 
Apply.process(newSplitter(), s); 
    } 
} 
classUpcaseextendsStringProcessor { 
publicStringprocess(Objectinput) { // Covariant return return ((String)input).toUpperCase(); 
    } 
} 
classDowncaseextendsStringProcessor { 
publicStringprocess(Objectinput) { 
return ((String)input).toLowerCase(); 
    } 
} 
classSplitterextendsStringProcessor { 
publicStringprocess(Objectinput) { 
returnArrays.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 packageinterfaces.interfaceprocessor; 
importinterfaces.filters.*; 
classFilterAdapterimplementsProcessor { 
Filterfilter; 
publicFilterAdapter(Filterfilter) { 
this.filter=filter; 
    } 
publicStringname() { returnfilter.name(); } 
publicWaveformprocess(Objectinput) { 
returnfilter.process((Waveform)input); 
    } 
} 
publicclassFilterProcessor { 
publicstaticvoidmain(String[] args) { 
Waveformw=newWaveform(); 
Apply.process(newFilterAdapter(newLowPass(1.0)), w); 
Apply.process(newFilterAdapter(newHighPass(2.0)), w); 
Apply.process( 
newFilterAdapter(newBandPass(3.0, 4.0)), w); 
    } 
}

/* Output:

Using Processor LowPass

Waveform 0

Using Processor HighPass

Waveform 0

Using Processor BandPass

Waveform 0

*///:~

在这种使用适配器的方式中,FilterAdapter的构造器接受你所拥有的接口Filter,然后生产具有你所需要的Processor接口的对象。你可能还注意到了,在FilterAdapter类中用到了代理。将接口从具体实现中解耦使得接口可以应用于多种不同的具体实现,因此代码也就更具可复用性。

目录
相关文章
|
1天前
|
NoSQL Java API
java一行代码实现RESTFul接口
Spring Data REST是构建在Spring Data之上的库,可自动将repository转换为REST服务,支持JPA、MongoDB、Neo4j、GemFire和Cassandra。无需手动创建Service和Controller层。要开始,需配置JPA数据源,创建实体类和Repository接口。快速实现REST接口,只需引入spring-boot-starter-data-rest Maven依赖,并在Repository接口上添加@RepositoryRestResource注解。
|
2天前
|
Java 程序员 数据格式
关于Java抽象类和接口的总结和一点个人的看法
关于Java抽象类和接口的总结和一点个人的看法
|
7天前
|
存储 安全 Java
[Java基础面试题] Map 接口相关
[Java基础面试题] Map 接口相关
|
8天前
|
Java
一文搞清楚Java中的包、类、接口
包、类、接口、方法、变量、参数、代码块,这些都是构成Java程序的核心部分,即便最简单的一段代码里都至少要包含里面的三四个内容,这两天花点时间梳理了一下,理解又深刻了几分。
31 10
|
12天前
|
Java 开发者
探索 Java 的函数式接口和 Lambda 表达式
【4月更文挑战第19天】Java 中的函数式接口和 Lambda 表达式提供了简洁、灵活的编程方式。函数式接口有且仅有一个抽象方法,用于与 Lambda(一种匿名函数语法)配合,简化代码并增强可读性。Lambda 表达式的优点在于其简洁性和灵活性,常用于事件处理、过滤和排序等场景。使用时注意兼容性和变量作用域,它们能提高代码效率和可维护性。
|
13天前
|
Java
Java接口中可以定义哪些方法?
【4月更文挑战第13天】
14 0
Java接口中可以定义哪些方法?
|
13天前
|
Java API
什么是Java函数式接口?
【4月更文挑战第13天】
15 0
什么是Java函数式接口?
|
15天前
|
设计模式 Java
Java接口与抽象类
Java接口与抽象类
17 0
|
19天前
|
安全 Java 编译器
接口之美,内部之妙:深入解析Java的接口与内部类
接口之美,内部之妙:深入解析Java的接口与内部类
37 0
接口之美,内部之妙:深入解析Java的接口与内部类
|
19天前
|
Java
Java中的抽象类和接口之间的异同点
抽象类顾名思义是之抽象出来的类,具有多个类的共性,是抽象的。抽象类和具体类是相对的概念。“抽象”是一种存在思想逻辑中的概念,而“具体”是一种可见可触摸的现实对象。简单说,法拉利,保时捷,兰博基尼,布加迪等多种车子,可以抽象出车,也可以抽象出跑车,这就是抽象出来的类别,称为抽象类。
10 0