一, 观察者模式的缺点
在之前的博文的介绍过观察者模式了.
观察者模式可以让多个观察者同时观察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();
}
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()");
}
}
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#委托的简洁.这是因为微软把很多底层的东西封装起来了, 更加方便程序猿的使用.