[Java编程思想] 第九章 接口

第九章 接口

  接口和内部类为我们提供了一种将接口与实现分离的更加结构化的方法。

9.1 抽象类和抽象方法

  为了不同的子类可以用不同的方式表示接口,可以创建一个通用接口,以此表示所有导出类的共同之处。另一种说法是将此类称作抽象基类。

  抽象方法,方法是不完整的,只有声明而没有方法体。

abstract void f();

  包含抽象方法的类叫做抽象类。如果包含一个或多个抽象方法,则该类必须被限定为抽象类。

abstract class A{
	abstract void f();
    void g(){System.out.println();}
}

  如果从一个抽象类继承,并想创建该类的对象,那么就必须为基类中的所有抽象方法提供方法定义。如果不这样做(可以选择不做),那么导出类也是抽象类,且编译器将会强制我们用abstract关键词来限定这个类。

9.2 接口

  interface关键字使抽象的概念更向前迈进了一步。abstract关键字允许人们在类中创建一个或多个没有任何定义的方法——提供了接口部分,但没有提供任何相应的具体实现,这些实现是由此类的继承者创建。interface这个关键字产生一个完全抽象的类,它根本就没有提供任何具体实现。接口提供了形式,而未提供任何具体实现。

  一个接口表示:“所有实现了该特定接口的类看起来都像这样”。但是,interface不仅是极度抽象的类,因为它允许通过创建一个能够被向上转型为多种基类的类型,来实现某种类似多重继承变种的特性。

  使用interface关键字代替class。接口也可以包含域,但是是隐式的static和final的。要让一个类遵循某个(或是一组接口)特定的接口,需要使用implement关键字,它表示:“interface只是它的外貌,但是现在我要声明它是如何工作的。”除此之外,它看起来很像继承。实现接口的类就变成普通的类。

  可以选择在接口中显式地将方法声明为public的,即使这样,它们也是public的。否则将只能得到默认的包访问权限,这样在继承过程中,可访问权限就降低了,这是Java编译器不允许的。

9.3 完全解耦

  如果一个方法操作的是类而非接口,那么只能用该类及其子类,如果想在此继承结构外使用这个方法是不行的。接口可以在很大程度上放宽限制,写出复用性更好的代码。

9.3.1 使用继承

  将继承了处理器的各种类型处理器放入应用中,输出不同结果,目前M.process()只接收处理器P及其导出类。创建按一个能根据所传递的参数对象的不同而具有不同行为的方法,称为策略设计模式,P对象就是一个策略,在main中可以看到三种不同类型策略应用。

// 处理器
class P{
	public String name(){
		return getClass().getSimpleName();
	}
	Object process(Object input){ return input; }
}
// 大写处理器
class A extends P{
	String process(Object input){
		return ((String)input).toUpperCase();
	}
}
// 小写处理器
class B extends P{
	String process(Object input){
		return ((String)input).toLowerCase();
	}
}
// 分割处理器
class S extends P{
	String process(Object input){
		return Arrays.toString(((String)input).split(" "));
	}
}
// 应用
public class M{
    // 接收各类型处理器
	public static void process(P p, Object s){
		System.out.println("Using Process " + p.name());
		System.out.println(p.process(s));
	}
	public static String s = "How are you";
	public static void main(String[] args){
		process(new A(), s);
		process(new B(), s);
		process(new S(), s);
	}
}

输出

Using Process A
HOW ARE YOU
Using Process B
how are you
Using Process S
[How, are, you]

9.3.2 另一种继承

  再创建一系列过滤器类,F和P具有相同接口元素,但因为它并非继承自P——因此你不能将F用于M.process()方法,这主要原因是因为M.process()方法和P之间的耦合过紧。这就使得应该复用M.process()的代码时,复用却被禁止了,并且它的输入输出都是W类型(注意过滤器类型W和处理器P没有任何关系,此处例子是单独的类库)。

// 过滤的类型,用于输入输出
class W{
	private static long counter;
	private final long id = counter++;
	public String toString(){ return "W" + id;}
}
// 过滤器类,同上面处理器P类结构相似
class F{
	public String name(){
		return getClass().getSimpleName();
	}
	public W process(W input){ return input; } 
}
// 低过滤器
class H extends F{
	double lowCutoff;
	public H(double lowCutoff){ this.lowCutoff = lowCutoff; }
	public W process(W input){
		return input;
	}
}
// 高过滤器
class I extends F{
	double highCutoff;
	public I(double highCutoff){ this.highCutoff = highCutoff; }
	public W process(W input){
		return input;
	}
}
// 带通过滤器
class J extends F{
	double lowCutoff, highCutoff;
	public J(double lowCut, double highCut){ 
		lowCutoff = lowCut;
		highCutoff = highCut;
	}
	public W process(W input){
		return input;
	}
}

9.3.3 使用接口

  如果P是一个接口,这些限制就会松动,使得你可以复用接口M.process():

interface P{
	String name();
	Object process(Object input);
}
public class M{
	public static void process(P p, Object s){
		System.out.println("Using Process " + p.name());
		System.out.println(p.process(s));
	}
}

  将P改为接口,再声明SP实现该接口,在SP中声明抽象方法process(),将SP作为其他类型的父类,由各自类型实现具体方法,M.process()就能接受这些类型。创建多个不同的SP类,M.process()就能实现复用。所有类由处理器接口P串连关系

// 处理器接口
interface P{
	String name();
	Object process(Object input);
}
// 应用
class M{
	public static void process(P p, Object s){
		System.out.println("Using Process " + p.name());
        // 在这里调用方法,通过中间类SP,其他实现了SP的类都可以调用
		System.out.println(p.process(s));
	}
}
// 处理
public abstract class SP implements P{
	public String name(){
		return getClass().getSimpleName();
	}
    // 抽象方法由各自类型实现
	public abstract String process(Object input);
	public static String s = "How are you";
	public static void main(String[] args){
        // 这些参数都继承于SP->P,而M.process()的参数又是P接口,所以可以通用
		M.process(new X(), s);
		M.process(new Y(), s);
		M.process(new Z(), s);
	}
}
// 大写处理器
class X extends SP{
	public String process(Object input){
		return ((String)input).toUpperCase();
	}
}
// 小写处理器
class Y extends SP{
	public String process(Object input){
		return ((String)input).toLowerCase();
	}
}
// 分割处理器
class Z extends SP{
	public String process(Object input){
		return Arrays.toString(((String)input).split(" "));
	}
}

输出

Using Process X
HOW ARE YOU
Using Process Y
how are you
Using Process Z
[How, are, you]

9.3.4 使用适配器

  但是,经常碰到的情况是无法修改你想要的类。在上面例子中,各种处理器类库是由你创建出来的,但是在[9.3.2 另一种继承]中,发现一种新的类库,可以使用适配器设计模式。实现复用M.process()

  适配器中的代码将接受你所拥有的接口,并产生你所需要的接口。目前的情况是,类型W及各种过滤器F与处理器P没有关系,不能使用通用的M,需要一个类,它实现了处理器接口P,并且能够接收和输出类型W。

  完整结构是这样理解:

1、通用处理器接口P
2、可以应用通用处理器的M
	M.process(p.process()):只能接受P
3、单独的类库:
	类型W,过滤器F,实现F的H、I、J它们都使用W做参和返回
4、为了让M能够接受P而创建的适配器FA
	FA有一个F
	FA实现了P,所以能用M.process(p.process(FA))
	FA有一个返回W类型的方法process(Obj),所以可以放入H、I、J
    FA.process()方法里调用F.process(H、I、J)
5、结果:
	H、I、j通过FA中的FA.process(F.process(W))进行一此类型转换,再去调用真正的M.process((FA)F.process((W)p.process()))

描述有点混乱,实际执行并不是一条线,而是适配器从中间插入,进行适配,接口上完成M.process调F.process

M.process(P)调FA.process()调F.process(W)调H.process()

// FA : FAdapter 能使用类型过滤器W的适配器,FA实现P,与P进行关联
class FA implements P{
	F filter;
	public FA(F f){
		this.filter = f;
	}
	public String name(){ return filter.name(); }
	// 返回值类型W,输入实现了类型W的各种过滤器,就可以通过FA适配器使用M.process()
	public W process(Object input){
		// 这里调用F.process(),实际执行是M调用FA.F.process(W);
		return filter.process((W)input);
	}
}
// 应用
public class Apply{
	public static void main(String[] args){
		W w = new W();
		// M.process本来只能用P类型,现在FA实现了P,H又实现了F,FA可以调F,所以是M.process(P)调FA.process()调F.process(W)调H.process()
		M.process(new FA(new H(1.0)), w);
		M.process(new FA(new I(2.0)), w);
		M.process(new FA(new J(3.0, 4.0)), w);
	}
}

输出

Using Process H
W0
Using Process I
W0
Using Process J
W0

  FA的构造器接受你所拥有的接口F,然后生成具有你所需要的P接口对象。

9.4 Java中的多重继承

  实现一个接口,不一定同时实现方法, 如果继承类中有相同的方法,也可以完整实现。

  使用接口的核心原因:为了能够向上转型为多个基类型。

9.5 通过继承来扩展接口

  一般情况下,只可以将extends用于但一类,但是可以引用多个基类接口。

interface A extends X, Y{
	void f();
}

9.6 接口中的域

  接口中的任何域都是static和final的,可以在作为创建常量组的工具,类似枚举enum,接口中的域自动是public的。

9.7 接口与工厂

  将实现和接口分离,可以透明地将某个实现替换为另一种实现。在工厂对象上调用创建方法,工厂对象将生成接口地某个实现对象。

  理解如下:

原本是
    Service->Implement->调用具体实现{固定一对一实现}
现在是
    Service->ServiceFactory{分离接口}
    Service->Implement{分离实现}
    ServiceFactory->ImplementionFactory
    调用ServiceFactory->Service->Implemention1Facotry->Implement{具体谁实现了Implement都没关系}
// 服务接口
interface Service{
	void method1();
	void method2();
}
// 工厂接口
interface ServiceFactory{
	Service getService();
}
// 实现1
class Implemention1 implements Service{
	Implemention1(){}
	public void method1(){ System.out.println("Implemention1 method1"); }
	public void method2(){ System.out.println("Implemention1 method2"); }
}
// 实现1的工厂,返回实现1
class Implemention1Facotry implements ServiceFactory{
	public Service getService(){
		return new Implemention1();
	}
}
// 实现2
class Implemention2 implements Service{
	Implemention2(){}
	public void method1(){ System.out.println("Implemention2 method1"); }
	public void method2(){ System.out.println("Implemention2 method2"); }
}
// 实现2的工厂,返回实现2
class Implemention2Facotry implements ServiceFactory{
	public Service getService(){
		return new Implemention2();
	}
}
public class Factory{
	// 接受符合Service服务的任意工厂实现
	public static void serviceConsumer(ServiceFactory fact){
		// 获取服务接口,调用方法的实现
		Service s = fact.getService();
		s.method1();
		s.method2();
	}
	public static void main(String[] args){
		// 在Service服务和Implemention实现外面都套了一层Factory工厂,分离接口和实现
		serviceConsumer(new Implemention1Facotry());
		serviceConsumer(new Implemention2Facotry());
	}
}

输出

Implemention1 method1
Implemention1 method2
Implemention2 method1
Implemention2 method2
posted @ 2022-03-27 16:50  蔚然丶丶  阅读(60)  评论(0编辑  收藏  举报