最近在将C#转换到Java语言,遇到了C#中的事件,一时间还不知道如何用Java处理。很显然,C#toJava工具是不具备这种自动翻译的能力的。首先还是搞清楚C#事件实现了什么功能。C#的事件其实本质的说来还是比较简单的,就是使用类似于C语言中的函数指针,指向不同的方法实体进行调用,只不过它是函数指针数组可以增加,可以减少。在C#中就是代理实现的,提供了类型安全的保障。使用过程如下图所示。
图1 事件的使用关系
看起来挺简单的,如果在一个类内,这里就不用所谓的事件了,可以直接通过函数调用来实现相同的功能。但是,如果是多个类(两个以上的类),那么就还是需要使用代理绑定,它提供了一定的灵活性,它可以让代理绑定任何方法,他只管在某一个条件下调用。这种也实现了好莱坞原则:“被调,而不是主动调用。”,一种框架中常用的手法,因为它给框架带来了很大的灵活性和扩展性。
本想自己写接口在Java中实现类似的功能,后来想到事件它可以有多个绑定方法,觉得可能不那么简单。于是想想这种类似于发布-订阅(或者观察者)模式在java中是否有实现。果不其然,在Java中有Observable类(作为事件的发出者,图中的类1)和Observer接口,类似于图中类2的接口。需要注意的是Observable类虽然是具体的类,但是不能直接使用,因为其中的protectedsynchronizedvoid setChanged();没有办法直接调用,所以notifyObservers()方法调用是并不会实现回调。
闲话到这里,我们用Java中的观察者模式来实现C#中的事件处理过程。
模拟场景:一个商场它会发布商品的价格信息,在价格发生变化是他会主动通知他的消费者。Merchant(商场类),Customer(顾客类)代码如下:
Merchant.java
packagejane.javaAndPattern.observer;
importjava.util.Observable;
/**
* @author haisujiang
* 商家类,它会发布商品的信息,并把信息推送给消费者。
*
*/
public class Merchant extends Observable{
private int price;
/**
* @param price
*/
public Merchant(int price) {
super();
this.price = price;
}
public Merchant() {
super();
price = 10; // 设置默认价格
}
public int getPrice() {
return price;
}
/**
* @param price 设置了新的价格,并且通知注册的消费者(或者说会员)
*/
public void setPrice(int price) {
this.price = price;
setChanged();
notifyObservers(price);
}
}
Customer.java
packagejane.javaAndPattern.observer;
importjava.util.Observable;
importjava.util.Observer;
/**
* @author haisujiang
* 消费者类,接收商品消息
*/
public class Customer implements Observer {
private String name ="";
/**
* @param name
*/
public Customer(String name) {
super();
this.name = name;
}
/* 被动调用的方法体
*/
@Override
public void update(Observable o, Object arg) {
// TODOAuto-generated method stub
System.out.println("我是"+name+",已经收到商品的最新价格 :"+ arg.toString());
}
public static void main(String[] args) {
Merchant merchant = new Merchant(100);
Customer jane = new Customer("Jane");
Customer kate = new Customer("Kate");
//Customer tom = new Customer("Tom");
merchant.addObserver(jane);
merchant.addObserver(kate);
System.out.println("价格变化,请各位顾客留意!");
merchant.setPrice(97);
System.out.println("价格变化,请各位顾客留意!");
merchant.setPrice(95);
}
}
运行一下可以看到update方法被调用了,但是呢?调用的顺序是条件的相反的顺序,也就是说是先通知kate后通知jane的,通过阅读源代码可以发现它默认是从后到前调用update方法的,子类可以重新实现notifyObservers (Object arg)来改变调用顺序。
在阅读《C#图解教程》时,书上举得例子是,时间事件源某个类在1秒钟处理一次方法,这样就存在一个问题:发布类如何确定1秒钟,调用某个事件呢?其实发布类它还是通过订阅事件计数器的一个间隔时间段的事件来实现的。具体代码如下:
class MyTimerClass
{
public eventEventHandler Elapsed;
private voidOnElapsed(Objectsender , EventArgsargs)
{
if(Elapsed != null)
{
Elapsed(sender,args);
}
}
//保证一秒调用一次
private TimermyTimer;
public MyTimerClass()
{
myTimer= new Timer(1000);
myTimer.Elapsed += new ElapsedEventHandler(myTimer_Elapsed);
myTimer.Enabled = true;
}
void myTimer_Elapsed(object sender, ElapsedEventArgs e)
{
OnElapsed(sender, e);
}
}
在这个方法中,我们可以看到两个事件:Timer.Elapsed和MyTimerClass. Elapsed;通过这种方式,我们可以看到时间的一种横向的往后面的类的流向形式,这种方式就有点类似于职责链模式。 为了模拟这种情况,我们不妨在前面的场景中继续进行扩展:比如消费者的朋友,对这个产品的价格比较关心。她可能对你(消费者)说:“价格便宜一点的时候告诉我一声呀!”那么你就应该将这个价格变化的消息告诉她。我们增加类CosumerFriend类:
importjane.javaAndPattern.observer.Customer.Eventarg;
importjava.util.Observable;
importjava.util.Observer;
/**
* @author
*
*/
public class CustomerFriend implements Observer{
/* (non-Javadoc)
* @seejava.util.Observer#update(java.util.Observable, java.lang.Object)
*/
@Override
public void update(Observable o, Object arg) {
// TODOAuto-generated method stub
Eventarg eg = (Eventarg)arg;
System.out.println("----作為"+eg.getName()+"的朋友,她告訴了我價格的最新變化:"+ eg.getPrice());
}
}
我们增加事件的信息类,Eventarg:包括事件消费者名称和价格。
class Eventarg{
private String name;
private int price;
public Eventarg(String name, int price) {
this.name = name;
this.price = price;
}
public String getName() { return name; }
public void setName(String name) {this.name = name; }
public int getPrice() { return price; }
public void setPrice(int price) { this.price = price; }
}
最终Customer变为:
/**
* @author haisujiang
* 消费者类,接收商品消息
*/
public class Customer extends Observable implements Observer {
private String name ="";
/**
* @param name
*/
public Customer(String name) {
super();
this.name = name;
}
/* 被动调用的方法体
* @seejava.util.Observer#update(java.util.Observable, java.lang.Object)
*/
@Override
public void update(Observable o, Object arg) {
System.out.println("我是"+name+",已经收到商品的最新价格 :"+ arg.toString());
setChanged();
notifyObservers(new Eventarg(name,Integer.parseInt(arg.toString())));
}
public void addFriend(CustomerFriend cf){
addObserver(cf);
}
// 测试
public static void main(String[] args) {
Merchant merchant = new Merchant(100);
Customer jane = new Customer("Jane");
Customer kate = new Customer("Kate");
//Customer tom = new Customer("Tom");
merchant.addObserver(jane);
merchant.addObserver(kate);
//假设kate有两个朋友:
kate.addFriend(new CustomerFriend());
kate.addFriend(new CustomerFriend());
System.out.println("价格变化,请各位顾客留意!");
merchant.setPrice(97);
System.out.println("价格变化,请各位顾客留意!");
merchant.setPrice(95);
}
}
继承设计可能是不恰当的、有害的,因为一个类中可能定义多个事件,所以更加合适的设计可能是组合设计,面向对象设计的原则也是首先考虑组合,再次是继承。至于修改,在此不展开了,也是非常简单的定义一个继承Observable的类,作为成员变量即可。修改后的Customer.java代码如下:
package jane.javaAndPattern.observer;
import java.util.Observable;
import java.util.Observer;
/**
* @author haisujiang
* 消费者类,接收商品消息
*/
public class Customer implements Observer {
private String name ="";
private Observable priceChanged;
/**
* @param name
*/
public Customer(String name) {
super();
this.name = name;
priceChanged = new Observable(){
@Override
public void notifyObservers(Object arg) {
setChanged();
super.notifyObservers(arg);
}
};
}
/* 被动调用的方法体
* @see java.util.Observer#update(java.util.Observable, java.lang.Object)
*/
@Override
public void update(Observable o, Object arg) {
// TODO Auto-generated method stub
System.out.println("我是"+name+",已经收到商品的最新价格 :"+ arg.toString());
// priceChanged.
priceChanged.notifyObservers(new Eventarg(name,Integer.parseInt(arg.toString())));
}
public void addFriend(CustomerFriend cf){
priceChanged.addObserver(cf);
}
//main方法不变(省略)
}
可以看到结果如下:
总结由Java实现C#中的 事件一般都是用一个继承自Observable类的成员变量来代替。
图 C#与Java事件对应的处理方式
上图中从上到下分别为:事件的声明,事件的发布,事件的订阅(注册),事件调用的具体方法。总结起来,与C#相比Java实现相应的功能需要增加两个类,一个作为事件的包装;一个作为具体调用方法的包装。