您的位置:首页 > 编程语言 > Java开发

Java 利用反射实现C#的委托

2015-01-03 21:37 375 查看

一, 观察者模式的缺点

在之前的博文的介绍过观察者模式了.

观察者模式可以让多个观察者同时观察1个被观察者.

也就说被观察者可以1次过执行所有观察者的update()方法.

再通俗d来讲, 就是多个观察者的Update()方法交给被观察者来执行了.



观察者主要应用在Gui 界面的控件事件上, 例如按个按钮可以,令多个其他控件同时产生变化.

但是观察者模式有1个限制, 就是所有观察者类必须实现观察者(Observer)接口. 这个也是回调(callback)方法的1个实现.

现实项目中, 相当于一部分类是不能修改的, 很可能是Jar包发布或者你没有修改权限. (封闭--开放原则)

如果某个类没有实现观察者(Observer)接口, 那么有没有1个办法将这个类的某个方法也交给被观察者来执行呢?

二, 一个题目

假如当前有两个类,

1个是类A, 里面有1个无返回值无参方法a().

另1个是类B, 里面有1无返回值个无参方法b().

这两个类没有实现任何接口, 也不能被修改.

要求写1个类S, 这个类S类似与观察者模式的被观察者(Subject), 可以添加若干个类A或者类B的对象), 并可以通知它们执行自己的a()/b() 方法.

如果让类A和类B实现观察者(Observer)是很简单的.

难点就是它们都不能被修改.

三, C#的委托实现

C#有一种类叫delegate(委托), 他可以让方法(函数)的本身作为1个参数.

同样, 委托也可以是1个容器, 用于存放不同的对象的方法(前提是这些方法返回值和参数类型一样)

C#是这样实现的.

3.1 类A

class A
{
public void a()
{
Console.WriteLine("A.a()");
}

}


3.2 类B

class B
{
public void b()
{
Console.WriteLine("B.b()");
}

}


3.3 类S

public delegate void DelegateNoPra();

class S
{
public DelegateNoPra dg;

public void sNotify()
{
dg();
}
}


注意, 这里的委托(delegate void DelegateNoPra是定义在类S的外面的, 也就是跟类S同级.

然后在类S里构造1个DelegateNoPra的对象dg.

这里的dg相当于1个容器.

在sNotify()调用dg(); 相当于调用dg容器内所有的传入的方法.

3.4 客户端代码
static void Main(string[] args)
{

S s = new S();
s.dg += new A().a;
s.dg += new B().b;
s.dg += new B().b;

s.sNotify();
Console.Read();
}

客户端代码很容易看懂,

先实例化1个S对象

然后将1个A对象的方法a, 两个B对象的方法b 放入S对象的委托容器.

最终一次过被执行

输出:

A.a()
B.b()
B.b()


注意, 传入的方法必须是无参方法(). 也就是将委托对象必须具有相同的参数类型和



如果有参数的方法怎么传入?

则必须再定义1个对应的delegate委托.



四, Java的反射实现

可以见到, C#的代码相当简洁.

而委托这种东西看起来比观察者模式更加方便.

但是现实上是先有观察者模式, 再有C#的委托的.

而且Java是没有委托(delegate)这种东西的, 但是java有反射, 利用java的反射特性也可以实现上面的功能.

4.1 类A

public class A {
public void a(){
System.out.println("A.a()");
}
}


4.2 类B

public class B {
public void b(){
System.out.println("B.b()");
}
}


4.3 类ObjMethod

这里稍稍想想, 到底如何将类A和类b的指定方法传入到另1个类呢?

其实我们可以把它拆分成两部分:

1. 传送对象本身(Object).

2. 传送方法的名字(String).

至于怎样把这两种不同类型的东西放入类S的容器? 方法有很多种,

这里我新建1个类ObjMethod, 把这两种东西封装在一起.

而且我是打算把它放入HashSet容器的, 所以重写了hashCode()和 equals()方法, 只要上面两个成员相等, 我们就认为是相同的两个对象.

代码:

public class ObjMethod {
private Object obj;
private String method;

public ObjMethod(Object obj, String method){
this.obj = obj;
this.method = method;
}

public String getMethod() {
return this.method;
}

public Object getObj() {
return this.obj;
}

@Override
public boolean equals(Object o){
ObjMethod m = (ObjMethod)o;
return (this.getObj() == m.getObj()) && (this.getMethod().equals(m.getMethod()));
}

@Override
public int hashCode(){
return this.getObj().hashCode() * this.getMethod().hashCode();
}
}


4.4 类S

类S的Notify()方法要用到反射了.

其实步骤很简单:

1. 从HashSet获取对象Obj 和 方法名method

2. 利用反射特性获得Obj的类.

3. 利用Obj的类和方法名获得那个方法.

4. 执行这个方法.

import java.util.HashSet;
import java.util.Iterator;
import java.lang.reflect.Method;
public class S {
private HashSet<ObjMethod> methodList = new HashSet<ObjMethod>();

public void attach(Object obj, String method){
this.methodList.add(new ObjMethod(obj,method));
}

public void detach(Object obj, String method){
this.methodList.remove(new ObjMethod(obj,method));
}

public void sNotify(){
if (this.methodList.isEmpty()){
return;
}

Iterator<ObjMethod> it = this.methodList.iterator();
while (it.hasNext()){
ObjMethod m = (ObjMethod)it.next();

Class<?> objClass = m.getObj().getClass(); //get the class of the object
try{
Method method = objClass.getMethod(m.getMethod(), new Class[]{}); //no any parameters
method.invoke(m.getObj(),new Object[]{});//no parameters
}catch(Exception e){
e.printStackTrace();
}

}
}

}


4.5 客户端代码

S s = new S();
s.attach(new A(), "a");
s.attach(new A(), "a");

B b1 = new B();
s.attach(b1, "b");
s.sNotify();

System.out.println("Step 2!");
s.detach(b1,"b");
s.sNotify();


代码也很容易看懂,

跟C#版本差不多. 只不过要把对象和方法名作为两个参数传入到类S对象的HashSet容器.

输出:

A.a()
B.b()
A.a()
Step 2!
A.a()
A.a()


上面的例子跟C#的委托一样, 同样要求传入的方法具有相同的参数类型.

当然, 再完善下甚至可以有一定限度地支持不同的参数类型.

五, 小结

利用Java的反射特性同样实现类似C#委托的功能, 当然代码看起来远远没有C#委托的简洁.
这是因为微软把很多底层的东西封装起来了, 更加方便程序猿的使用.
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: