一、观察者模式
C#和Java这两大流行的编程语言,相似程度极高。本文就来欣赏一下C#委托和Java回调的异曲同工之妙。
设计模式中有一个非常著名的“观察者模式”,又称为“发布-订阅模式”。大概意思是一方发布消息,另一方订阅消息。订阅消息的一方不必等待消息的到来,而是径自干自己的事情去。如果发布消息的一方发布了新的消息,就会自动通知订阅者接收。
其实不必在意这种设计模式叫什么,事实上所谓的“观察者模式”的代码经常出现在我们编写的程序中,而直到计划写这篇文章时我才知道原来这就是大名鼎鼎的“观察者模式”。所以说编程的套路都是人们经验的结晶,之后不过再套上个炫酷的名字而已。
言归正传,下面我们分别用C#和Java设计出发布-订阅的效果。
二、订阅者部分
通常订阅者都是用户,对于用户来说,打算订阅一种消息,最简单又合理的做法应该是:
- 告诉发布者我要订阅哪一条消息;
- 把我的消息处理方法提交给发布者。
这就好比订阅一份报纸时,先告诉报社我要订哪一种报纸,再告诉报社我家邮筒的地址。
让我们先用C#试试如何实现:
namespace PublisherSubscriberDemo
{
class Program
{
static void Main(string[] args)
{
Publisher publisher = new Publisher();
publisher.SomeMessage += message => { Console.WriteLine("Subscriber 1: " + message); };
publisher.SomeMessage += message => { Console.WriteLine("Subscriber 2: " + message); };
publisher.Start();
}
}
}
为了使代码简洁,我们利用C#的lambda表达式把上面提到的两步工作写到了一行中(否则就要单独定义消息处理函数)。这行代码的效果是,把等号右侧的lambda表达式所代表的消息处理方法添加到发布者的SomeMessage
消息的订阅列表中。发布者的订阅列表SomeMessage
是一个委托(delegate),它可以添加所有订阅者的消息处理方法。这里我们让订阅者订阅了两次该消息,以代表多个订阅者的情况。
如果用Java实现大概是这个样子:
public class PublisherSubscriberDemo {
public static void main(String[] args) {
Publisher publisher = new Publisher();
publisher.addOnMessageArriveListener(message -> { System.out.println("Subscriber 1:" + message); });
publisher.addOnMessageArriveListener(message -> { System.out.println("Subscriber 2:" + message); });
publisher.start();
}
}
代码量基本与C#相同。这里使用了Java 8的lambda表达式使代码更简洁(否则就要用到匿名内部类)。由于Java中没有委托,所以只能用发布者提供的addOnMessageArriveListener
把消息处理方法传递进去。
三、发布者部分
对于发布者来说,需要做的工作也有两条:
- 接收订阅者的消息处理方法;
- 在适当的时候调用这些方法。
其效果就相当于订阅者的消息处理方法在消息抵达的时候自动执行了,而实质上则是发布者手动调用了这些方法。
先来看C#的代码实现:
namespace PublisherSubscriberDemo
{
class Publisher
{
public delegate void Message(string message);
public Message SomeMessage;
public void Start()
{
while (true)
{
SomeMessage("Some message from publisher.");
System.Threading.Thread.Sleep(1000);
}
}
}
}
是不是感觉很清爽?C#在编码效率上做的非常贴心,这里用的委托,可以接收任意数量的消息处理方法,只要这些方法和Message
的参数及返回值相同。并且在调用SomeMessage
时只需要写一次,就可以调用所有订阅者的消息处理方法。
相比之下,Java的代码就显得臃肿一些:
public class Publisher {
private List<MessageArriveListener> listeners = new ArrayList<MessageArriveListener>();
public interface MessageArriveListener {
void MessageArrived(String message);
}
public void addOnMessageArriveListener(MessageArriveListener listener) {
listeners.add(listener);
}
public void start()
{
while (true) {
for (MessageArriveListener listener : listeners) {
listener.MessageArrived("Some message from publisher.");
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
我们需要手动设计一个List
来保存所有的消息处理方法(在Java中通常称为监听器),然后在适当的时候遍历这个列表一一通知订阅者。当然,Java的优点是代码语法简单,容易理解。比如所谓的监听器就是一个自定义接口,订阅者传进来的消息处理方法必须是一个实现该接口的类(通常是匿名内部类或lambda表达式)。
四、运行结果
C#代码和Java代码的运行效果完全一致,都是每秒钟打印两句话。
演示代码已上传GitHub仓库:
C#:https://github.com/jingedawang/PublisherSubscriberDemo-CSharp
Java:https://github.com/jingedawang/PublisherSubscriberDemo-Java
五、参考资料
c# event关键字的意义 lulu_jiang
Java Lambda表达式入门 Constantin Marian Alin