第九章 接口

2013年6月20日 星期四 21时41分40秒

第九章 接口
接口和内部类为我们提供了一种将接口与实现分离的更加结构化的方法。
9.1 抽象类和抽象方法
java提供了一种抽象方法的机制(相当于C++中的虚函数),这种方法是不完整的,仅有声明而没有方法体。
abstract void f();
包含抽象方法的类叫抽象类。如果一个类包含一个或多个抽象方法,该类必须被定义为抽象的。
如果从一个抽象类基础,并想创建该新类的对象,那么必须为基类中的所有抽象方法提供方法定义。如果不这么做,那么导出类便也是抽象类,且编译器会强制我们用abstract关键字来 限定这个类。
创建抽象类和抽象方法很有用,应该他们可以使类的抽象性明确起来,并告诉用户和编译器打算怎么使用他们。抽象类还是很有用的重构工具。

9.2 接口
interface关键字使抽象的概念更向前迈进了一步。abstract关键字允许人们在类中创建一个或多个没有任何定义的方法----提供了接口部分。但是没有提供任何相应的具体实现,这些 实现是由此类的继承者创建的。
interface不仅仅是一个极度抽象的类,因为它允许人们通过创建一个能够被向上转型为多种基类的类型,来实现某种类似多重继变种的特性。
一个接口,如果不添加public关键字,则它只具有包访问权限,这样它就只能在一个包内使用。接口也可以包含域,但是这些域隐式地是static和final的。
接口中的方法默认都是public类型。

9.3 完全解耦
只要一个方法操作的是类而非接口,那么你就只能使用这个类及其子类。如果你想要将这个方法应用于不在此继承结构中的某个类,那么你可以选择使用接口。这样可以编写可复用性 更好的代码。
package chapter9.classprocessor;
import java.util.Arrays;
/*@name Bath.java
* @describe 9.3 完全解耦
* @since 2013-06-20 22:14
* @author 张彪
*/
class Processor{
public String name(){
return getClass().getName();
}
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 Processor "+p.name());
System.out.println(p.process(s));
}
public static String s="Disagreement with be liefs is by definition incorrect";
public static void main(String[] args){
process(new Upcase(),s);
System.out.println("================");
process(new Downcase(),s);
System.out.println("================");
process(new Splitter(),s);
}
}

/*Using Processor chapter9.classprocessor.Upcase
DISAGREEMENT WITH BE LIEFS IS BY DEFINITION INCORRECT
================
Using Processor chapter9.classprocessor.Downcase
disagreement with be liefs is by definition incorrect
================
Using Processor chapter9.classprocessor.Splitter
[Disagreement, with, be, liefs, is, by, definition, incorrect]*/

如上例所示,创建一个能够根据所传参数对象的不用而具有不同行为的方法,被称为策略设计模式。这类方法包含所要执行的算法中固定不变的部分,而"策略"包含变化的部分。策略就是传递进去的参数对象,它包含要执行的代码。这里Processor对象就是一个策略。


package chapter9.interfaces;
import chapter9.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(1.0)), w);
Apply.process(new FilterAdapter(new BandPass(3.0,4.0)), w);
}
}

在上述这种使用适配器的方式中,FilterAdapter的构造器接收了你所拥有的接口Filter,然后生成具有你所需要的Processor接口对象。你可能还注意到了,在FilterAdapter类中用到了代理。

将接口从具体实现类中解耦使得接口可以应用于多种不同的具体实现,因此代码也就更具有复用性。

9.4 Java中的多重继承
接口不仅仅只是一种更纯粹的抽象类,它的目标比这要高。因为接口是根本没有任何具体实现的----也就是说,没有任何与接口相关的存储;因此也就无法阻止多个接口的组合。
Java中可以继承多个接口,并可以向上转型为每个接口,因为每个接口都是一个独立类型。

下面的例子展示了一个具体类组合数个接口之后产生了一个新类:
package chapter9.interfaces;
/*@name Bath.java
* @describe 9.4 Java中的多重继承
* @since 2013-06-20 23:30
* @author 张彪
*/
interface CanFight{void fight();}
interface CanSwim{void swim();}
interface CanFly{void fly();}
class ActionCharacter{public void fight(){}}
//注意:當通過這種方式講一個具體類和多個接口組合到一起時,這個具體的類必須放在前面,後面跟著才是接口(負責編譯器會報錯)
class Hero extends ActionCharacter implements CanFight,CanSwim,CanFly {
public void swim(){}
public void fly(){}
}
public class Adventure {
public static void t(CanFight x){x.fight();}
public static void u(CanSwim x){x.swim();}
public static void v(CanFly x){x.fly();}
public static void w(ActionCharacter x){x.fight();}
public static void main(String[] args){
Hero h=new Hero();
t(h);
v(h);
u(h);
w(h);
}
}

注意:在Adventure类中,Hero对象可以被传递给四个方法中的任何一个,这意味着它依次被向上转型为每一个接口。

上述例子展示了使用接口的原因:为了能够向上转型为多个基类的类型(以及由此而带来的灵活性)。然而,使用接口的第二个原因却是与使用接口基本相同:我们应该使用接口还是抽象类??如果要创建不带任何方法定义和成员变量的基类,我们就应该选择接口而不是抽象类。事实上,如果知道某个事物应该成为一个基类,那么第一选择应该使他成为一个接口。

9.5 通过继承来扩展接口
通过继承,可以很容易在接口中添加新的方法声明,还可以通过继承在新接口中组合数个接口。这两种情况都可以获得新的接口。
package chapter9.interfaces;
/*@name Bath.java
* @describe 9.5 通過繼承來擴展接口
* @since 2013-06-20 23:55
* @author 张彪
*/
interface Monster{void menace();}
interface DangerousMonster extends Monster{void destory();}
interface Lethal{void kill();}
class DragonZilla implements DangerousMonster{
public void menace(){}
public void destory(){}
}
interface Vampire extends DangerousMonster{
void drinkBlood();
}
class VeryBadVampire implements Vampire{
public void menace(){}
public void destory(){}
public void kill(){}
public void drinkBlood(){}
}

public class HorrorShow {
static void u(Monster b){b.menace();}
static void v(DangerousMonster d){
d.menace();
d.destory();
}
static void w(Vampire l){l.drinkBlood();}
public static void main(String[] args){
DangerousMonster h=new DragonZilla();
u(h);
v(h);
Vampire p=new VeryBadVampire();
u(p);
v(p);
}
}

9.5.1 组合接口时的名字冲突
在实现多重继承时,可能会碰到一个小陷阱,在前面的例子中,CanFight和ActionCharacter都一个一个相同的void fight()方法。这不是问题的所在,因为方法在二者中是相 同的。相同的方法不会有什么问题,但是如果签名或返回类型不同呢??? 举一个例子
package chapter9.interfaces;
/*@name Bath.java
* @describe 9.5.1 組合接口時的名字衝突
* @since 2013-06-21 0:14
* @author 张彪
*/
interface I1 { void f(); }
interface I2 { int f(int i); }
interface I3 { int f(); }
class C { public int f() { return 1; } }

class C2 implements I1, I2 {
public void f() {}
public int f(int i) { return 1; } // overloaded
}

class C3 extends C implements I2 {
public int f(int i) { return 1; } // overloaded
}

class C4 extends C implements I3 {
// Identical, no problem:
public int f() { return 1; }
}

//class C5 extends C implements I1 {} //error: The return type is incompatible with I1.f(),C.f()
//interface I4 extends I1, I3 {} //error: The return type is incompatible with I3.f(),I1.f()
当撤销注释行时,就显示错误信息

所以在打算组合不同的接口中使用相同的方法名通常会造成代码的可读性的混乱,请尽量避免这种情况。

9.6 适配接口
接口最吸引人的原因之一就是允许同一个接口具有多个不同的具体实现。在简单的情况下,它的体现形式通常是一个接受接口类型的方法,而该接口的实现和向该方法传递的对象则取 决于方法的使用者。
因此,接口的一种常见用法就是前面提到的策略设计模式,此时你编写一个执行某些操作的方法,而该方法将接受一个同样是你指定的接口。你主要就是声明:“你可以用任何你想要 的对象来调用我的方法,只要你的对象遵循我的接口。”这使得你的方法更灵活,通用,并更具有可复制性。
下面以Readable接口为例:
package chapter9.interfaces;
import java.io.IOException;
import java.nio.CharBuffer;
import java.util.*;
/*@name Bath.java
* @describe 9.6 適配接口
* @since 2013-06-21 0:47
* @author 张彪
*/
public class RandomWords implements Readable{
private static Random rand=new Random(47);
private static final char[] capitals="ABCDEFGHIGKLMNOPQRSTUVWXYZ".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) throws IOException {
if(--count==0){
return -1;
}
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 6;
}
public static void main(String[] args){
Scanner s=new Scanner(new RandomWords(10));
while(s.hasNext()){
System.out.println(s.next());
}
}
}

假设你还有一个未实行Readable的类,怎样才能让Scanner组用于它呢? 下面这个例子,它可以随机产生浮点数。
package chapter9.interfaces;
import java.util.Random;
/*@name RandomDoubles.java
* @describe 产生随机浮点数
* @since 2013-06-21 9:45
* @author 张彪
*/
public class RandomDoubles {
private static Random rand=new Random(47);
public double next(){
return rand.nextDouble();
}
public static void main(String[] args){
RandomDoubles rd=new RandomDoubles();
for(int i=0;i<7;i++){
System.out.println(rd.next());
}
}
}


我们再次使用了适配器模式,但是在本例中,被适配的类可以通过继承和实现Readable接口来创建。因此通过使用interface关键字提供的伪多重继承机制,我们还可以生成既是RandomDoubles又是Readable的新类。
package chapter9.interfaces;
import java.nio.CharBuffer;
import java.util.Scanner;
public class AdapterRandomDoubles extends RandomDoubles implements Readable{
private int count;
public AdapterRandomDoubles(int count){
this.count=count;
}
public int read(CharBuffer cb){
if(count--==0){
return -1;
}
String result=Double.toString(next())+" ";
cb.append(result);
return result.length();
}
public static void main(String[] args){
Scanner s=new Scanner(new AdapterRandomDoubles(10));
while(s.hasNext()){
System.out.println(s.nextDouble()+" ");
}
}
}


9.7 接口中的域
因为你放入接口中的任何于都自动是static 和 final的,所以接口就成为了一种很便捷的用来创建常量组的工具。在Java SE5之前,这是产生与C或C++中enum(枚举类型)具有相同效 果的类型的唯一途径。 因此在Java SE5之前的代码中你会看到这样的代码:
package chapter9.interfaces;
public interface Months {
public interface Month{
int
JANUARY=1,FEBRUARY=2,MARCH=3;
}
}
注意:接口中的域自动是public。
有了Java SE5之后,你就可以使用更强强大而灵活的enum关键字。(详见19章)

9.7.1 初始化接口中的域
在接口中定义的域不能是“空final”,但是可以被非常量表达式初始化。例如:
package chapter9.interfaces;
import java.util.Random;
/*@name RandomDoubles.java
* @describe 9.7.1 初始化接口中的域
* @since 2013-06-21 9:45
* @author 张彪
*/
public interface RanVals {
Random RAND=new Random(47);
int RANDOM_INT =RAND.nextInt(10);
long RANDOM_LONG=RAND.nextLong()*10;
float RANDOM_FLOAT=RAND.nextFloat();
double RANDOM_DOUBLE=RAND.nextDouble();
}

//既然域是static的,他们就可以在类第一次被加载时初始化,这发生在任何域首次被访问时。当然这些域不是接口的一部分,他们的值被存储在该接口的静态存储区域内。

9.8 嵌套接口
接口可以嵌套在类或其他接口中。这揭示了许多非常有趣的特性。
package chapter9.interfaces;
/*@name RandomDoubles.java
* @describe 9.8 嵌套接口
* @since 2013-06-21 11:21
* @author 张彪
*/
//类嵌套
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();}
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 receieD(D d){
dRef=d;
dRef.f();
}
}
//接口嵌套
interface E{
interface G{void f();}
public interface H{void f();}
void g();
//The interface member type I can only be public
//private interface I{}
}

public class NestingInterfaces {
public class BImp implements A.B{
public void f(){}
}
class CImp implements A.C{
public void f(){}
}
//cannot implements a private interface
/*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();
a.receieD(a.getD());
}
}

//NestingInterfaces展示了嵌套接口的各种实现方式。特别要注意的是,当实现某个接口时,并不需要实现嵌套在其内部的任何接口。而且,private接口不能在定义它的类之外被实现。


9.9 接口与工厂
接口是实现多重继承的途径,而生成遵循某个接口的对象的典型方法就是工厂设计模式。这与直接调用构造器不同,我们在工厂对象上调用的是的创建方法,而该工厂对象将生成接口 的某个实现的对象。 理论上通过这种方式,我们的代码将完全与接口的实现分离,这就使得我们可以可以透明地将某个实现替换为另一个实现。
下面的实例展示了工厂方法的结构。
package chapter9.interfaces;
/*@name RandomDoubles.java
* @describe 9.9 接口与工厂___工厂方法设计模式
* @since 2013-06-23 16:21
* @author 张彪
*/
interface Service{
void method1();
void method2();
}
interface ServiceFactory{
Service getService();
}
class Implementation1 implements Service{
public void method1(){System.out.println("Implementation1.method1()");}
public void method2(){System.out.println("Implementation1.method2()");}
}
class Implementation2 implements Service{
public void method1(){System.out.println("Implementation2.method1()");}
public void method2(){System.out.println("Implementation2.method2()");}
}
class Implementation1Factory implements ServiceFactory{
public Service getService(){
return new Implementation1();
}
}
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 Implementation2Factory());
}
}

为什么我们想要添加这种额外级别的间接性呢?一中常见的原因是想要创建框架。


9.10 总结
“确定接口是理想选择,因而应该总是选择接口而不是实现。”这其实是一种引诱。当然,对于创建类,几乎在任何时刻,都可以替代为创建一个接口和一个工厂。
任何抽象性都应该是应真正的需求而产生的。当必要时,你应该重构接口而不是到处添加额外级别的间接性,并由此带来的额外复杂性。
恰当的原则应该是优先选择类而不是接口。从类开始,如果接口的必须性变得非常明确,那么就进行重构。接口是一种重要的工具,但是他们容易被滥用。



2013-06-23 16:42 记 @tangxiacun.tianhequ.guanzhou
欧姆龙FINS(工厂集成网络系统)协议是专为该公司自动化设备间数据交互而设计的网络通信标准。该协议构建于TCP/IP基础之上,允许用户借助常规网络接口执行远程监控、程序编写及信息传输任务。本文档所附的“欧ronFins.zip”压缩包提供了基于C与C++语言开发的FINS协议实现代码库,旨在协助开发人员便捷地建立与欧姆龙可编程逻辑控制器的通信连接。 FINS协议的消息框架由指令头部、地址字段、操作代码及数据区段构成。指令头部用于声明消息类别与长度信息;地址字段明确目标设备所处的网络位置与节点标识;操作代码定义了具体的通信行为,例如数据读取、写入或控制器指令执行;数据区段则承载实际交互的信息内容。 在采用C或C++语言实施FINS协议时,需重点关注以下技术环节: 1. **网络参数设置**:建立与欧姆龙可编程逻辑控制器的通信前,必须获取控制器的网络地址、子网划分参数及路由网关地址,这些配置信息通常记载于设备技术手册或系统设置界面。 2. **通信链路建立**:通过套接字编程技术创建TCP连接至控制器。该过程涉及初始化套接字实例、绑定本地通信端口,并向控制器网络地址发起连接请求。 3. **协议报文构建**:依据操作代码与目标功能构造符合规范的FINS协议数据单元。例如执行输入寄存器读取操作时,需准确配置对应的操作代码与存储器地址参数。 4. **数据格式转换**:协议通信过程中需进行二进制数据的编码与解码处理,包括将控制器的位状态信息或数值参数转换为字节序列进行传输,并在接收端执行逆向解析。 5. **异常状况处理**:完善应对通信过程中可能出现的各类异常情况,包括连接建立失败、响应超时及错误状态码返回等问题的处理机制。 6. **数据传输管理**:运用数据发送与接收函数完成信息交换。需注意FINS协议可能涉及数据包的分割传输与重组机制,因单个协议报文可能被拆分为多个TCP数据段进行传送。 7. **响应信息解析**:接收到控制器返回的数据后,需对FINS响应报文进行结构化解析,以确认操作执行状态并提取有效返回数据。 在代码资源包中,通常包含以下组成部分:展示连接建立与数据读写操作的示范程序;实现协议报文构建、传输接收及解析功能的源代码文件;说明库函数调用方式与接口规范的指导文档;用于验证功能完整性的测试案例。开发人员可通过研究这些材料掌握如何将FINS协议集成至实际项目中,从而实现与欧姆龙可编程逻辑控制器的高效可靠通信。在工程实践中,还需综合考虑网络环境稳定性、通信速率优化及故障恢复机制等要素,以确保整个控制系统的持续可靠运行。 资源来源于网络分享,仅用于学习交流使用,请勿用于商业,如有侵权请联系我删除!
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值