Ⅸ 接口
9.1抽象类和抽象方法
抽象类是为了建立一种基本形式,以此表示所有导出类的公共部分。创建一个抽象类的对象是没有意义的,为了阻止使用者这么做,编译器会报错,以此保证抽象类的纯粹性。请记住,创建抽象类是希望通过这个通用接口操作一系列类。
包含一个或多个抽象方法的是抽象类,但是抽象类也可以包含非抽象方法。
如果一个类继承自一个抽象类,并想要创建其对象,那么必须实现基类中所有的抽象方法,否则,导出类还是一个抽象类,无法实例化对象,并且编译器会提醒我们使用abstract修饰。
9.2 接口
interface关键字使抽象的概念迈进了一步,interface关键字产生一个完全抽象的类,这个类不会提供任何具体实现。interface不仅仅是一个极度抽象的类,因为它允许人们通过创建一个能够被向上转型为多种基类的类型(实现多个接口),来实现某种类似于多继承的特性。
接口也可以包含域,并且这些域隐式地是final和static,这些域以前是被用作常量,后来当枚举类型出现后,这种方式已经弃而不用了,只需要了解就行了,以放在维护某些祖传代码时遇到。
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()代码。这是因为Processor和Apply.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中,只能继承一个父类,若要获得更多能力,可以实现多个接口。而接口本身是没有任何具体实现的——也就是说,没有任何与接口相关的存储。(请注意,这段话中,“接口”在不同语境下有着不同的含义,请勿混淆)
使用接口的核心原因:
- 为了能向上转型为多个基类型(这点将带来非常大的灵活性)
- 防止客户端程序员创建该类的对象。
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 接口中的域
前文提到:放入接口中的任何域都是隐式的static和final,所以接口就成为一种很便捷的用来创建常量组的工具。当然,在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();
}
}
假如没有工厂,你在想要使用接口的具体某个实现时,必须在业务代码的某处指明。在这个模式中,服务与工厂一一对应。那为什么需要这种额外的间接?
- 优点:
- 利用工厂的工厂方法类去创建具体的产品对象,隐藏了具体产品对象的创建细节,只需要关心具体产品对应的具体工厂; 2.遵守开闭原则。加入新的产品类时,只需要同时加入工厂类就可以实现扩展,无需修改原来的代码。
- 缺点:
- 随着产品种类的数量的增长,工厂类也会随之增加,将不利于系统的维护,增加系统编译和运行的开销。
小白的成长探索之路。