zoukankan      html  css  js  c++  java
  • 第三章 第三节 剖析SWT的设计原则

    第三节  剖析SWT的设计原则

    返回目录

    在第一章我们已经介绍过,SWT使用底层操作系统提供的本地控件库,它仅仅是程序与底层系统交互Java接口。本地控件的生命期(lifecycle)就像是Java控件对象的一个镜中像:创建Java控件的时候,本地控件同时被创建;销毁Java控件的时候,本地控件也被销毁了。这种设计避免了一种情况的出现,就是底层控件还没有创建的时候调用代码控件的方法(method)。这种情况在其他的工具包(toolkit)中是存在的,使得本地控件与代码控件的生命周期不一致[2]

    举个例子,看看MFC(Microsoft Foundation Classes)的两步式创建过程。要创建一个按钮,代码如下:

    CButton button; // 在栈上创建C++对象

    button.Create(<parameters>); // 创建窗口对象

    假如在C++对象和(构建于本地窗口控件之上的)本地窗口控件的创建代码之间插入代码,例如:

    CButton button; //在栈上创建C++对象

    CString str = _T("Hi"); //创建一个 CString ,记录按钮的文本

    button.SetWindowText(str); // 设置按钮的文本出问题了

    button.Create(<parameters>); //创建窗口对象

    代码在编译的时候不会报错,但运行时与开发时不同。调试时,会触发断言(assertion),而release版本的行为是未定义的。

    控件的容器[3]

    多数GUI要求,在创建一个控件之前要指出它的容器;在控件的生命期中,它是属于那个容器的[4]。容器的生命周期决定了控件的生命周期。另外,许多本地控件有详细的特性,也就是样式(style),这个必须在创建的时候设定。例如,一个按钮(button)可以是一个点击按钮(push button),也可以是一个复选框(checkbox)。因为构建SWT控件时要创建它对应的本地控件,所以它必须把这个信息传递给它的构造函数(constructor)SWT控件通常需要两个参数:容器和样式(a parent and a style)。容器是类org.eclipse.swt.widgets.Widget或它的子类;可选的类型是在SWT类中预先定义好的整数常量(integer constant)。可以传递单个样式,也可以用位或(OR)合并多种样式。在本书中,我们讲解一个控件的时候,会同时介绍它的样式。

    销毁控件

    Swing的开发者把本节的内容作为SWT低等的一个证据,并以此嘲笑。通常,Java的开发者在这里确实会觉得不适应甚至讨厌,因为本节的内容是:编程者要自己做清除工作。这个概念,受到Java的开发者嫌弃,因为它抛开了Java垃圾回收机制,而倒退到很久以前那种要开发者负责的情况。

    为什么要我销毁对象?Java的垃圾收集机制极好地管理着内存,但是GUI资源管理运作于重重约束之下。可用的GUI资源数目是非常受限的,而且,在很多平台上,是整个系统的限制。由于SWT直接与本地底层图形资源一起工作,每个SWT资源需要一个GUI资源,所以及时地释放资源不仅可以提升当前SWT程序的性能,也会提示正在运行的其它GUI程序的性能。Java的垃圾收集机制没有时间保障,因此会造成图形资源的不良管理。正因为如此,编程者必须自己负起责任。

    这个任务有多重?事实上,一点都不重。在SWT的一系列文章中,Carolyn Macleod Steve Northover描述了两条简单的规则[5]

    • 谁创建,谁销毁
    • 父已亡,子亦亡[6]

    规则1:谁创建,谁销毁

    在本章的开头,我们已经知道SWT对象创建的时候,本地资源也会被创建。换句话说,调用SWT对象的构造函数的同时,底层的本地资源被创建。假设您编写了下面的代码,您已经构建了一个SWTColor对象,也在GUI底层平台分配了一个color资源:

    Color color = new Color(display, 255, 0, 0);   //创建红色

    根据规则1,您创建了它,所以您使用完后必须销毁它,如下:

    color.dispose();       //我创建,我销毁

    然而,如果您不是调用构造函数获得一个资源,您不需销毁这个资源。例如,考虑下面的代码:

    Color color = display.getSystemColor(SWT.COLOR_RED); // 取到红色

    再次地,您有了一个Color对象,它容纳了底层平台的红色资源,但不是您申请(allocate)的。根据规则1,您不该销毁它。为什么不能?它不属于您——您是借过来的,其它的对象(object)可能正在使用它或将要用到。销毁这样一个资源会代理惨重的损失。

    规则2:父已亡,子亦亡

    对每一个用new创建出来的SWT对象,都要调用dispose()将很快变得枯燥,并可能使SWT处于边缘地位。所幸,SWT的设计者意识到了这一点,他们创建了一个自动销毁的逻辑级连[7]。一个容器被销毁的时候,它的所有控件也会被销毁。这意味着一个Shell被销毁的时候,所有属于它的控件也被自动销毁了。您将看到,在“hello, World”程序中,尽管用构造函数创建了一个Label对象,但从来不会调用label.dispose()。当用户关闭Shell的时候,Label对象自动地被销毁了。

    您可能觉得您从不需要调用dispose(),本节纯属浪费空间。确实,可能在您写的很多程序中,资源都有容器,它们会自动的被销毁。那么,考虑一下这种情况:您想改变Text控件的字体。您的代码可能如下:

    Text text = new Text(shell, SWT.BORDER); // 创建text

    Font font = new Font(display, "Arial", 14, SWT.BOLD); // 创建字体

    text.setFont(font); // 设置字体

    您创建的字体(Font)对象没有容器,所以不会自动销毁,甚至是Shell关闭以及使用它的Text对象销毁的时候。您可能觉得要自己销毁font是个负担,但要认识到text与销毁font没有什么关系——它并不拥有font。事实上,您可能把一个Font对象应用于多个控件;自动销毁会带来严重的后果。

    忽略已销毁的对象

    机敏的读者可能已经注意到本章讨论的镜像生命期中有个间隙。这些情况下会怎样:封装了本地控件的Java对象仍然在有效范围内(is still in scope),但它属于的Shell对象已被销毁了;或者一个控件的销毁函数已被人为的调用了——本地控件被销毁了?底层的本地控件不存在的情况下,不能调用这个Java对象的函数?

    确实是这样。如果调用一个本地控件已被销毁的控件的函数,会惹不少的麻烦。一旦一个控件被销毁了,即使它仍在有效范围内,不该对它做任何事情。是的,这个Java对象是可用的,但底层的对等体已被销毁了。如果试图对一个已被销毁的控件做什么,会得到一个SWTException,内容是“控件已被销毁[8]”。看看清单3-2的代码。

    import org.eclipse.swt.*;

    import org.eclipse.swt.layout.*;

    import org.eclipse.swt.widgets.*;

    public class Broken

    {

      public static void main(String[] args)

      {

        Display display = new Display();

        Shell shell = new Shell(display);

        shell.setLayout(new RowLayout());

        Text text = new Text(shell, SWT.BORDER);

        shell.open();

        while (!shell.isDisposed())

        {

         if (!display.readAndDispatch())

         {

          display.sleep();

         }

        }

        System.out.println(text.getText()); // 出错!

        display.dispose();

        }

      }

    清单3-2

    代码能编译和运行,但在主窗口关闭之后,会在控制台打印如下的stack trace

    org.eclipse.swt.SWTException: Widget is disposed

     at org.eclipse.swt.SWT.error(SWT.java:2332)

     at org.eclipse.swt.SWT.error(SWT.java:2262)

     at org.eclipse.swt.widgets.Widget.error(Widget.java:385)

     at org.eclipse.swt.widgets.Control.getDisplay(Control.java:735)

     at org.eclipse.swt.widgets.Widget.isValidThread(Widget.java:593)

     at org.eclipse.swt.widgets.Widget.checkWidget(Widget.java:315)

     at org.eclipse.swt.widgets.Text.getText(Text.java:705)

     at Broken.main(Version.java:24)

    再罗嗦几句。如果在Windows XP上运行这个程序,会跳出一个对话框,说“javaw.exe遇到一个问题,需要关闭”,还要问您“是否发送错误报告给Microsoft?”

    本小节的内容是很简单的:一旦一个对象被销毁,不管是显式调用了dispose()函数,还是它的容器被销毁了,就不要再理它(leave it alone)



    [2] 译注:原文This design avoids issues with calling methods on a code object when the underlying widget hasn't yet been created, which can occur in other toolkits that don't match the lifecycles of the code widget and the native widget.

    [3] Parent,译者觉得翻译为“容器”更形象。

    [4] 译注:原文Most GUIs require you to specify a parent for a widget before creating that widget, and the widget "belongs" to its parent throughout its lifecycle.

    [5] 原注:Carolyn MacLeod and Steve Northover, SWT: The Standard Widget Toolkit—Part 2: Managing Operating System Resources, www.eclipse.org/articles/swt-design-2/swt-design-2.html.

    [6] 译注:原文If you created it, you dispose it.  Disposing the parent disposes the children.

    [7] 译注:原文a logical cascade of automatic disposal

    [8] 译注:原文Widget has been disposed

    返回目录

  • 相关阅读:
    jchdl
    jchdl
    UVa 10256 (判断两个凸包相离) The Great Divide
    UVa 11168 (凸包+点到直线距离) Airport
    LA 2572 (求可见圆盘的数量) Kanazawa
    UVa 10652 (简单凸包) Board Wrapping
    UVa 12304 (6个二维几何问题合集) 2D Geometry 110 in 1!
    UVa 10674 (求两圆公切线) Tangents
    UVa 11796 Dog Distance
    LA 3263 (平面图的欧拉定理) That Nice Euler Circuit
  • 原文地址:https://www.cnblogs.com/ols/p/2173303.html
Copyright © 2011-2022 走看看