Live软件开发面面谈——事件

Posted starrow

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Live软件开发面面谈——事件相关的知识,希望对你有一定的参考价值。

第2章  事件

在计算机科学里,事件指的是系统内发生的某件事或变化,可以被某个程序接收并处理。它可以是用户输入导致的,例如按键、单击鼠标;可以是网络通讯导致的,例如Web服务器接收到一个请求,邮件服务器收到一封邮件;也可以仅仅作为不同对象之间控制流程转移的一种手段,例如我们为程序自定义的事件。所有这些情况都被抽象出一套共同的机制,用以有效地处理事件参与者之间的互动。这个机制包含以下几个组成部分:事件的源/发布者、事件的收听者/订阅者/处理器以及收听者与发布者之间如何处理事件的协议,包括收听者用于处理事件的方法的签名、发布者传递给收听者的事件信息。事件机制在图形界面软件开发、网络编程等领域都有广泛的应用,围绕它进行的编程范式被称为事件驱动编程。

事件与编程中的许多其他概念既有联系也有区别,如控制反转(Inversion of control)、回调函数(Callback function)和观察者模式(Observer pattern)。把事件和它们放在一起讨论和比较有助于我们更清楚地理解各自的内涵和用途。之后本章将重点分析Java、C#和javascript三种语言中事件编程的不同实现方式和特点,以更充分地揭示事件的本质,并且例示一个理念在不同语言中相映成趣的表现形式——这既能体现理念的一般性,又极好地展示了编程语言由于设计之差异在解决问题的方式和表现力上的多样性。

2.1  控制反转

所谓控制反转,是针对程序正常的控制流程而言的。一般情况下,正在运行的函数或对象的方法调用另一个函数或对象的方法,控制也就从调用方转移到被调用方,直到被调用方运行完毕,才返回给调用方。但是某些情况下,需要被调用方中途将控制传递回调用方,这种控制转移的方向与正常方向相反的现象就称为控制反转。最常见的有以下几种情况。

  1. 被调用方需要一直运行,无法返回,而在不确定的时间又要运行调用方的逻辑。图形用户界面程序的开发就是很好的例子。程序员使用图形用户界面的通用类库里的控件创建视图,视图一直运行,收听用户操作触发的事件。用户什么时候输入文本框、点击按钮是不确定的。当这些事件发生时,视图则要通过事件的处理程序,执行项目特定的业务逻辑。
  2. 被调用方运行时间较长,调用方不愿或者不能等待被调用方执行完成。正常的控制流程下,在被调用方执行完毕返回前,调用方一直等待,即处于所谓阻塞状态。假如采用控制反转的模式,将调用方等待被调用方返回后要运行的逻辑以某种方式传递给被调用方,然后新开一个线程,让被调用方在其中运行,调用方就可以保有控制,去做其他事情。函数的异步调用就是这种情况。
  3. 被调用方是提取多个特定程序中重用的公共的逻辑,被调用时还需要补充原来程序中特定的逻辑。例如JavaScript中Array的forEach()、map()等方法,将对一个列表数据结构的遍历逻辑提取出来,被调用时需要传入一个函数,以实现循环中特定的逻辑。

控制反转发生的共同前提是:调用方是项目特定的代码,被调用方是具有某种功能的通用程序,在开发中无法也不应该被修改。否则若被调用方也是一般的项目(ad hoc)代码,当它需要访问调用方的功能时,就可以直接在代码中加入,控制的转移也就是正常的。

比如对于以上第一种情况,假如图形用户界面的类库不是通用的,而是程序员每开发一个项目从头写出的,每个控件都是独一无二的,那就可以直接在一个按钮的实现代码内部添加它要处理的事件的响应程序。应用程序运行时,控件执行事件处理程序时也仅仅是调用自己的一个方法。这么极端的情况当然不会发生,一种缓和的变体却是可能的,并且实实在在地存在。这种情况下,控件仍然来自现成的类库,往视图上添加的却不是它们的实例,而是实例化自它们的继承类,在继承类中添加了事件处理程序。这样控件执行事件处理程序时,也没有将控制返回给它的调用方。理论上,开发图形用户界面程序时,确实可以采用这种方式,实际上android的用户界面框架还特意提供了这种途径,作为控件基类的View有若干公开的方法,例如onTouchEvent(),当一个按钮被按时,这个方法就会被系统调用。所以要为按钮添加响应该事件的逻辑,可以在按钮的继承类中实现这个方法。然而现实中没有多少程序员会采用这种途径,因为采用事件发布者和订阅者的模式,只需使用现成的控件,添加事件处理程序和调用一个方法一样简单,而为每个控件实例都创建一个继承类就繁琐得多。由这些讨论也可以从反面看出,事件实现的控制反转对图形用户界面程序开发来说,是一种多么有效和重要的模式。

对于第二种情况,假如被调用方是普通的项目代码,调用方不愿等待它运行完毕后返回,仍然要创建新的线程,但是不必将被调用方返回后要运行的逻辑再传递给它,因为此时被调用方和调用方一样,也在程序员的控制之下,直接将这些逻辑写在被调用方中就可以了。

2.2  观察者模式

在面向对象的语言中,为了在上述的第一种情况中(不确定何时要从被调用方运行调用方的逻辑)实现控制反转,常常会应用观察者模式。该模式的含义是,一个对象的内部状态发生变化时,通知另一些感兴趣的对象。前者称为主体,后者称为观察者。具体到代码上,主体内部保持一个观察者的列表,程序通过调用主体的addObserver或deleteObserver方法向其增加或删除观察者,当主体的内部逻辑引发状态变化时,调用自身的notifyObservers方法,该方法遍历观察者列表,分别调用它们的notify方法,将主体作为参数传递给观察者,观察者就可以依据主体的状态变化作出相应的动作。两者的关系如图2.1所示。

图2.1  观察者模式

与事件编程做对比,主体可以被看作事件发布者,观察者是收听者,notify方法是具体的事件处理程序,主体作为参数被传递给notify方法所以又是事件信息。将观察者模式以事件编程的语言来改写,就会得到类似下面的代码。

//事件发布者
/// starrow.demo.event.observer.EventPublisher
package starrow.demo.event.observer;

import starrow.event.EventInfo;
import starrow.event.IEventListener;

import java.util.ArrayList;
import java.util.Collection;

public abstract class EventPublisher 
	protected Collection<IEventListener> listeners = new ArrayList<IEventListener>();

	public EventPublisher() 

	

	public void addEventListener(IEventListener listener) 
		listeners.add(listener);
	

	public void removeEventListener(IEventListener listener) 
		listeners.remove(listener);
	

	protected void fireEvent() 
		EventInfo eventInfo = new EventInfo("Subject", this);
		for (IEventListener listener : listeners) 
			try 
				listener.handleEvent(eventInfo);
			 catch (Exception e) 
				e.printStackTrace();
			
		
	


///>

//事件收听者接口
/// starrow.event.IEventListener
package starrow.event;

/**
 * @author Starrow
 * Any custom listener must implements this interface.
 */
public interface IEventListener 
	void handleEvent(EventInfo eventInfo);

///>

//事件发布者传递给收听者的信息
/// starrow.event.EventInfo
package starrow.event;

import java.util.HashMap;

/**
 * @author Starrow
 *         This class contains information relevant to the event,
 *         and is passed to the handler of the listener.
 */
public class EventInfo 
    //A string name used to distinguish the event.
    String name;
    //The event publisher.
    Object source;
    //A map used to hold event relevant information.
    HashMap<String, Object> info;


    public EventInfo(String name, Object source, HashMap<String, Object> info) 
        this.name = name;
        this.source = source;
        this.info = info;
    

    public EventInfo(String name, Object source) 
        this(name, source, new HashMap<String, Object>());
    

    public String getName() 
        return name;
    

    public Object getSource() 
        return source;
    

    public HashMap<String, Object> getInfo() 
        return info;
    

    //Export the get method of the inner map.
    public Object get(String key) 
        return info.get(key);
    

    //Export the put method of the inner map.
    public EventInfo put(String key, Object value) 
        info.put(key, value);
        return this;
    


///>

//测试事件编程
/// starrow.demo.event.observer.TestEvent
package starrow.demo.event.observer;

import org.junit.Test;
import starrow.event.EventInfo;
import starrow.event.IEventListener;

import static java.lang.System.out;

public class TestEvent 

	public class Subject extends EventPublisher 
		public void run() 
			fireEvent();
		
	

	public TestEvent() 

	

	@Test
	public void testObserverEvent() 
		Subject subject = new Subject();
		subject.addEventListener(new IEventListener() 
			@Override
			public void handleEvent(EventInfo ea) 
				out.println("ObserverEvent was fired.");
			
		);
		subject.run();
	


///>

2.3  Java中的事件编程
2.3.1  通用的事件发布者和收听者
2.3.2  通用事件收听者的问题
2.3.3  Swing用户界面里的事件编程
2.3.4  专用事件收听者的问题
2.3.5  彻底地面向对象
2.3.6  Java 8带来的福音
2.3.7  这一切背后仍然是对象
2.4  C#中的事件编程
2.4.1  代理
2.4.2  事件
2.5  JavaScript中的事件编程
2.6  事件编程的其他细节
2.6.1  收听者的执行顺序
2.6.2  收听者是否在单独的线程执行
2.6.3  控件层次中的事件传播

更多内容,请参看拙著:

《Live软件开发面面谈》(京东)

《Live软件开发面面谈》(当当)

《Live软件开发面面谈》(天猫)

以上是关于Live软件开发面面谈——事件的主要内容,如果未能解决你的问题,请参考以下文章

Live软件开发面面谈——接口

Live软件开发面面谈——MVC

jQuery 2.0.3 源码分析 事件绑定 - bind/live/delegate/on

微信小程序 live-pusher 实时音视频录制 组件

Winform 事件

onclick 事件在 iPhone 混合应用程序中发生了两次