面向对象设计与事件

来源:互联网 发布:js 一分钟倒计时代码 编辑:程序博客网 时间:2024/05/16 05:23

代码单元

我开始接触Java时,对HelloWorld在Java里的写法不习惯:

public class HelloWorldClass {public static void main(String[] args){HelloWorldClass hw=new HelloWorldClass();hw.voice();}public void voice(){System.out.println("Hello World!");}}
奇怪为什么要这样大费周章,在main方法里创建一个对象,再调用它的方法。而不是像在C等语言里那样简单:

int main(void)

{

   printf("Hello, world!\n");

   return 0;

}

后来渐渐明白了,作为彻底实践面向对象设计的编程语言,Java的核心概念是对象,代码的组织单元也是对应的类。在C这样的过程式语言里,代码的基本单元是过程(函数)。C++虽然引入了面向对象的设计,仍然允许以过程的方式组织代码。代码单元的差别意味着在C等过程式语言里,一般的语句(除了声明变量和初始化等)都要写在某个函数里;而在Java里,一般的语句不仅要写在某个函数里,而且每个函数都要位于某个类里(即作为类的方法),并且除了特殊情况使用静态方法,普通的方法都是类被创建的实例上调用的。

事件

面向对象的设计给编程带来很大的便利和好处,在贯彻这种原则的语言中把类作为代码单元似乎也是自然的事情。但是实践中,剥夺了过程的独立生存权有时又会让代码失去过去的灵活和便捷。对事件的编程就是这样的场合。

事件是编程中广泛应用的概念和模式。单击窗体上的某个按钮触发一段代码就是一个简单而典型的例子。从编程的角度说,事件涉及到这样几个范畴:发布者(publisher)、收听者(listener)、事件参数(event argument)和处理程序(event handler)。发布者声明某个名称的事件,收听者登记响应程序;发布者触发事件,将事件的各种信息作为参数传递给处理程序,并执行。发布者、收听者和事件参数都可以对象的形式建模,唯独事件处理程序最直接和自然的形式就是一个函数。在C里,处理程序是一个回调函数;在JavaScript这类函数式语言里,处理程序就是简单的一个函数对象;在汲取了Java在经验和教训而设计的C#里,处理程序仍然是一个函数,只不过以代理包装。但是在一切都是对象的Java里,处理程序无法单独被传递,而是被包装在类——收听者——中,登记到发布者里。

Java里的事件编程

Java的思路是这样的,发布者命名一个事件,包含添加和删除事件处理程序的方法,其参数是某个EventListener的子接口MyEventListener,规定了处理程序的签名,如public void myEventOccurred(Object sender, EventArgs ea);收听者执行该接口,包含处理程序,藉addEventListener之类的方法登记到发布者里;事件触发时,发布者依据该事件对应的收听者的列表,将事件参数传递给收听者实现接口的方法,并运行。

我们来看看Oracle官方网站上Java教程里“How to Write a MouseListener”一文里对编写鼠标事件响应程序的范例(http://docs.oracle.com/javase/tutorial/uiswing/events/mouselistener.html):

public class MouseEventDemo ... implements MouseListener {        //where initialization occurs:        //Register for mouse events on blankArea and the panel.        blankArea.addMouseListener(this);        addMouseListener(this);    ...    public void mousePressed(MouseEvent e) {       saySomething("Mouse pressed; # of clicks: "                    + e.getClickCount(), e);    }    public void mouseReleased(MouseEvent e) {       saySomething("Mouse released; # of clicks: "                    + e.getClickCount(), e);    }    public void mouseEntered(MouseEvent e) {       saySomething("Mouse entered", e);    }    public void mouseExited(MouseEvent e) {       saySomething("Mouse exited", e);    }    public void mouseClicked(MouseEvent e) {       saySomething("Mouse clicked (# of clicks: "                    + e.getClickCount() + ")", e);    }    void saySomething(String eventDescription, MouseEvent e) {        textArea.append(eventDescription + " detected on "                        + e.getComponent().getClass().getName()                        + "." + newline);    }}
这个示例用到的两个类,MouseEventDemo.java(http://docs.oracle.com/javase/tutorial/uiswing/examples/events/MouseEventDemoProject/src/events/MouseEventDemo.java)和BlankArea.java(http://docs.oracle.com/javase/tutorial/uiswing/examples/events/MouseEventDemoProject/src/events/BlankArea.java)本身设计得有些混乱,发布者、收听者和处理程序都写在一个类MouseEventDemo里。因为是示例,视图、模型和控制器的代码也混合在一处,我把它们分开,改写成三个类:View、Model和Controller,可以更清晰地看出Java事件编程的逻辑。运行结果如下图所示。
package starrow;import java.awt.Color;import java.awt.Dimension;import java.awt.GridLayout;import javax.swing.BorderFactory;import javax.swing.JComponent;import javax.swing.JFrame;import javax.swing.JLabel;import javax.swing.JPanel;import javax.swing.JScrollPane;import javax.swing.JTextArea;import javax.swing.UIManager;import javax.swing.UnsupportedLookAndFeelException;public class View {    //static final String NEWLINE = System.getProperty("line.separator");        JFrame frame;JLabel blankArea;/**     * Create the GUI and show it.  For thread safety,     * this method should be invoked from the     * event dispatch thread.     */    public View() {        //Create and set up the window.        frame = new JFrame("MouseEventDemo");        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);           //Create and set up the content pane.        JComponent pane = new JPanel(new GridLayout(0,1));                blankArea = new JLabel();        blankArea.setBackground(Color.YELLOW);        blankArea.setOpaque(true);        blankArea.setBorder(BorderFactory.createLineBorder(Color.black));                pane.add(blankArea);                JTextArea textArea = new JTextArea();        textArea.setEditable(false);        JScrollPane scrollPane = new JScrollPane(textArea);        scrollPane.setVerticalScrollBarPolicy(                JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);        scrollPane.setPreferredSize(new Dimension(200, 75));        pane.add(scrollPane);                pane.setPreferredSize(new Dimension(450, 450));        pane.setBorder(BorderFactory.createEmptyBorder(20,20,20,20));                pane.setOpaque(true); //content panes must be opaque        frame.setContentPane(pane);         }        public void show(){        /* Use an appropriate Look and Feel */        try {          UIManager.setLookAndFeel("javax.swing.plaf.metal.MetalLookAndFeel");        } catch (UnsupportedLookAndFeelException ex) {            ex.printStackTrace();        } catch (IllegalAccessException ex) {            ex.printStackTrace();        } catch (InstantiationException ex) {            ex.printStackTrace();        } catch (ClassNotFoundException ex) {            ex.printStackTrace();        }        /* Turn off metal's use of bold fonts */        UIManager.put("swing.boldMetal", Boolean.FALSE);        //Display the window.        frame.pack();        frame.setVisible(true);    }}
package starrow;import java.awt.event.MouseEvent;import java.awt.event.MouseListener;public class Model implements MouseListener{    private String eventDescription="";            public void mousePressed(MouseEvent e) {        this.eventDescription="Mouse pressed (# of clicks: "                + e.getClickCount() + ")";        eventOutput(this.eventDescription, e);    }        public void mouseReleased(MouseEvent e) {        this.eventDescription="Mouse released (# of clicks: "                + e.getClickCount() + ")";        eventOutput(this.eventDescription, e);    }        public void mouseEntered(MouseEvent e) {        this.eventDescription="Mouse entered";        eventOutput(this.eventDescription, e);    }        public void mouseExited(MouseEvent e) {        this.eventDescription="Mouse exited";        eventOutput(this.eventDescription, e);    }        public void mouseClicked(MouseEvent e) {        this.eventDescription="Mouse clicked (# of clicks: "                + e.getClickCount() + ")";        eventOutput(this.eventDescription, e);    }    private void eventOutput(String eventDescription, MouseEvent e) {        System.out.println(eventDescription + " detected on "                + e.getComponent().getClass().getName()                + ".");    }}
package starrow;import java.awt.event.MouseListener;public class Controller {public static void main(String[] args) {// Schedule a job for the event dispatch thread:// creating and showing this application's GUI.View view = new View();Model model = new Model();// Register for mouse events on blankArea.view.blankArea.addMouseListener((MouseListener) model);//Show the view.view.show();}}

为了简便和清晰,和原来的程序相比,只注册了黄色空白区域的鼠标事件,事件发生时产生的消息从Java控制台打印【注1】。

可以这样用类包装事件处理程序的缺点是,对每一个或每一组事件都要声明和实现一个接口,如果是像鼠标事件这样的一组事件,收听者就必须实现接口里的每个事件的响应方法;理论上对一个发布者就需要写一个收听者类,因为多个发布者的相同事件的处理程序不能并存于同一个类里,例如两个按钮的单击事件处理程序不同,但是一个实现了鼠标事件接口的收听者里只能包含一个mouseClicked方法,这样不能把单个视图里各个控件的事件处理程序包含在一个模型类里,而需要多个类,使代码变得臃肿。为了安全和简单,Java没有保留函数指针,编写事件响应程序时藉接口来保证方法符合该事件处理程序应有的签名。与之相比,.Net平台引入的代理可实现对单个方法签名的校验和作为事件处理程序登记到发布者,在保障安全的同时,更为简洁。

C#里的事件编程

我们以C#为展示语言来看看.Net平台里的事件编程。C#的思路是这样的,发布者publisher声明一个事件,定义一个新的代理以规定该事件处理程序的签名,如public delegate void MyEvent Handler(Object sender, EventArgs ea);收听者listener包含符合签名的处理程序MyEventHandlerMethod,藉publisher.MyEvent +=new MyEventHandler(listener.MyEventHandlerMethod)的通用事件处理程序登记语法添加到发布者该事件的处理程序列表里;事件触发时,发布者将事件参数依次传递给对应列表里的处理程序,并运行。

和上面的Java示例功能相同的C#代码如下,同样也分为视图、模型和控制器三个类,不过视图的代码因为Windows Forms采用partial class技术,分成Visual Studio自动生成的View.Designer.cs和包含自定义代码的View.cs。运行时状况参看下图。

namespace MVCDemo{    partial class View    {        /// <summary>        /// Required designer variable.        /// </summary>        private System.ComponentModel.IContainer components = null;        /// <summary>        /// Clean up any resources being used.        /// </summary>        /// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>        protected override void Dispose(bool disposing)        {            if (disposing && (components != null))            {                components.Dispose();            }            base.Dispose(disposing);        }        #region Windows Form Designer generated code        /// <summary>        /// Required method for Designer support - do not modify        /// the contents of this method with the code editor.        /// </summary>        private void InitializeComponent()        {            this.components = new System.ComponentModel.Container();            this.textBox1 = new System.Windows.Forms.TextBox();            this.lblArea = new System.Windows.Forms.Label();            this.SuspendLayout();            //             // textBox1            //             this.textBox1.Dock = System.Windows.Forms.DockStyle.Bottom;            this.textBox1.Location = new System.Drawing.Point(0, 132);            this.textBox1.Multiline = true;            this.textBox1.Name = "textBox1";            this.textBox1.ScrollBars = System.Windows.Forms.ScrollBars.Both;            this.textBox1.Size = new System.Drawing.Size(284, 130);            this.textBox1.TabIndex = 0;            //             // lblArea            //             this.lblArea.BackColor = System.Drawing.Color.Yellow;            this.lblArea.Dock = System.Windows.Forms.DockStyle.Fill;            this.lblArea.Location = new System.Drawing.Point(0, 0);            this.lblArea.Name = "lblArea";            this.lblArea.Size = new System.Drawing.Size(284, 132);            this.lblArea.TabIndex = 1;            this.lblArea.Text = "Mouse event registered";            //             // View            //             this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 12F);            this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;            this.ClientSize = new System.Drawing.Size(284, 262);            this.Controls.Add(this.lblArea);            this.Controls.Add(this.textBox1);            this.Name = "View";            this.Text = "MVC Demo";            this.ResumeLayout(false);            this.PerformLayout();        }        #endregion        private System.Windows.Forms.TextBox textBox1;        internal System.Windows.Forms.Label lblArea;    }}

using System;using System.Collections.Generic;using System.ComponentModel;using System.Data;using System.Drawing;using System.Linq;using System.Text;using System.Windows.Forms;namespace MVCDemo{    public partial class View : Form    {        public View()        {            InitializeComponent();        }    }}

using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Windows.Forms;namespace MVCDemo{    class Model    {        public void AreaMouseEnter(Object sender, EventArgs ea)        {            //log = "mouse enter";            PrintEventMessage("Mouse entered", sender);        }        public void AreaMouseLeave(Object sender, EventArgs ea)        {            PrintEventMessage("Mouse exited", sender);        }        public void AreaMouseDown(Object sender, MouseEventArgs ea)        {            PrintEventMessage("Mouse pressed (# of clicks: " + ea.Clicks + ")", sender);        }        public void AreaMouseClick(Object sender, MouseEventArgs ea)        {            PrintEventMessage("Mouse clicked (# of clicks: " + ea.Clicks + ")", sender);        }        public void AreaMouseUp(Object sender, MouseEventArgs ea)        {            PrintEventMessage("Mouse released (# of clicks: " + ea.Clicks + ")", sender);        }        void PrintEventMessage(string message, Object sender)        {            string msg = message + " detected on " + sender.GetType().Name + ".";            Console.WriteLine(msg);        }    }}

using System;using System.Collections.Generic;using System.Linq;using System.Text;namespace MVCDemo{    class Controller    {        public static void Main()        {            Model model = new Model();            View view = new View();            view.lblArea.MouseEnter += new EventHandler(model.AreaMouseEnter);            view.lblArea.MouseLeave += new EventHandler(model.AreaMouseLeave);            view.lblArea.MouseDown += new System.Windows.Forms.MouseEventHandler(model.AreaMouseDown);            view.lblArea.MouseClick += new System.Windows.Forms.MouseEventHandler(model.AreaMouseClick);            view.lblArea.MouseUp += new System.Windows.Forms.MouseEventHandler(model.AreaMouseUp);            //view.Show();            System.Windows.Forms.Application.Run(view);        }    }}

从模型和控制器的代码可看出以代理的形式编写和登记单个事件处理程序的自由。鼠标进入、离开、按下、点击、松开这五个事件,在C#里可按需要只编写特定的处理程序,并且在同一个模型里还可以添加视图上其它控件的事件处理程序。


注1:在分开视图和模型的情况下,要像原示例那样在窗体上打印出消息,最有效的设计是绑定窗体上的文本框控件和模型里的某个属性。但是不像在.Net里数据绑定是框架提供的功能,Java运行库并没有类似的特性,旨在BeansBinding的JSR 295在2006年提出后迟迟没有进展,2011年被撤销了(https://jcp.org/en/jsr/detail?id=295);网上有一些实现Swing控件和模型数据绑定的类库,比如JGoodies的(介绍http://www.javalobby.org/java/forums/t17672)。为了省事,我在代码里就没有用了。


4 1
原创粉丝点击