【问题标题】:Avoiding global variables in Genie在 Genie 中避免全局变量
【发布时间】:2016-04-10 05:52:05
【问题描述】:

下面是 Genie 中 ToolbarButton 的工作代码。目标是获取所选文件的 uri 并将其返回给类的构造/初始化。问题是,在我遇到的所有示例中,都使用了全局 _variables(如下面的代码所示)。它看起来不直观,我担心每当代码变大时,消除错误就会变得更加困难,因为这些变量会开始累积。是否有任何其他方法可以使函数 openfile 将 uri 返回到类的构造/init 中的常规变量?

代码如下:

uses
    Granite.Widgets
    Gtk

init
    Gtk.init (ref args)

    var app = new Application ()
    app.show_all ()
    Gtk.main ()

// This class holds all the elements from the GUI
class Application : Gtk.Window

    _view:Gtk.TextView
    _uri:string

    construct ()

        // Prepare Gtk.Window:
        this.window_position = Gtk.WindowPosition.CENTER
        this.destroy.connect (Gtk.main_quit)
        this.set_default_size (400, 400)


        // Headerbar definition
        headerbar:Gtk.HeaderBar = new Gtk.HeaderBar()
        headerbar.show_close_button = true
        headerbar.set_title("My text editor")

        // Headerbar buttons
        open_button:Gtk.ToolButton = new ToolButton.from_stock(Stock.OPEN)
        open_button.clicked.connect (openfile)

        // Add everything to the toolbar
        headerbar.pack_start (open_button)
        show_all ()
        this.set_titlebar(headerbar)

        // Box:
        box:Gtk.Box = new Gtk.Box (Gtk.Orientation.VERTICAL, 1)
        this.add (box)

        // A ScrolledWindow:
        scrolled:Gtk.ScrolledWindow = new Gtk.ScrolledWindow (null, null)
        box.pack_start (scrolled, true, true, 0)

        // The TextView:
        _view = new Gtk.TextView ()
        _view.set_wrap_mode (Gtk.WrapMode.WORD)
        _view.buffer.text = "Lorem Ipsum"
        scrolled.add (_view)

    def openfile (self:ToolButton)

        var dialog = new FileChooserDialog ("Open file",
                                        this,
                                        FileChooserAction.OPEN,
                                        Stock.OK,     ResponseType.ACCEPT,
                                        Stock.CANCEL, ResponseType.CANCEL)
        //filter.add_pixbuf_formats ()
        //dialog.add_filter (filter)

        case dialog.run()
            when ResponseType.ACCEPT
                var filename = dialog.get_filename()
                //image.set_from_file(filename)

        if (dialog.run () == Gtk.ResponseType.ACCEPT)
            _uri = dialog.get_uri ()
            stdout.printf ("Selection:\n %s", _uri)

        dialog.destroy ()

或者我不应该担心 _variables 的累积?

【问题讨论】:

    标签: genie


    【解决方案1】:

    首先是术语注释,然后是概括。

    “全局变量”可以在程序中的任何位置访问,因此它的作用域是全局的。您在问题中所指的 _variables 是您的对象范围内的私有字段。它们只能由该对象中定义的代码访问。但是,您有权关注对象中私有工作变量的累积。

    设计对象很难做到,技术和想法在几十年的实践和研究中不断发展。由 Michael Feathers 介绍的 SOLID 首字母缩略词总结了面向对象设计的五个原则,为评估您的设计提供了有用的标准。还有本书,Design Patterns: Elements of Reusable Object-Oriented Software,作者 Gamma 等人。并于 1994 年首次出版,对面向对象编程中的设计进行了很好的总结和分类。该书使用文档编辑器作为案例研究来演示此类模式的使用。书中的 SOLID 原则和设计模式都是抽象的,它们不会告诉你如何编写程序,但它们确实给出了一组通用的想法,供程序员讨论和评估。所以我将在我的回答中使用这两种工具,但请注意近年来已经开发了其他技术来进一步增强软件开发过程,特别是 test driven developmentbehaviour driven development

    SOLID 中的 S 代表 Single Responsibility Principle,是查看示例的良好起点。通过调用您的对象Application,并将私有工作变量视为全局变量,这表明您在单个对象中编写整个应用程序。您可以做的是开始将Application 分成许多不同的对象,这些对象更多地关注单一的责任领域。首先,虽然我想我会重命名 Application 对象。我去了EditorWindow。在我下面的示例中,EditorWindow 也有一个Header 和一个DocumentView

    编译下面的代码:

    valac -X -DGETTEXT_PACKAGE --pkg gtk+-3.0 text_editor_example.gs

    -X -DGETTEXT_PACKAGE 的使用在本回答的末尾有说明。

    [indent=4]
    uses
        Gtk
    
    init
        Intl.setlocale()
        Gtk.init( ref args )
    
        var document = new Text( "Lorem Ipsum" )
    
        var header = new Header( "My text editor" )
        var body = new DocumentView( document )
        var editor = new EditorWindow( header, body )
    
        var document_selector = new DocumentFileSelector( editor )
        var load_new_content_command = new Load( document, document_selector )
        header.add_item( new OpenButton( load_new_content_command ) )
    
        editor.show_all()
        Gtk.main()
    
    class EditorWindow:Window
        construct( header:Header, body:DocumentView )
            this.window_position = WindowPosition.CENTER
            this.set_default_size( 400, 400 )
            this.destroy.connect( Gtk.main_quit )
    
            this.set_titlebar( header )
    
            var box = new Box( Gtk.Orientation.VERTICAL, 1 )
            box.pack_start( body, true, true, 0 )
            this.add( box )
    
    class Header:HeaderBar
        construct( title:string = "" )
            this.show_close_button = true
            this.set_title( title )
    
        def add_item( item:Widget )
            this.pack_start( item )
    
    class OpenButton:ToolButton
        construct( command:Command )
            this.icon_widget = new Image.from_icon_name(
                                                     "document-open",
                                                     IconSize.SMALL_TOOLBAR
                                                     )
            this.clicked.connect( command.execute )
    
    class DocumentView:ScrolledWindow
        construct( document:TextBuffer )
            var view = new TextView.with_buffer( document )
            view.set_wrap_mode( Gtk.WrapMode.WORD )
            this.add( view )
    
    interface Command:Object
        def abstract execute()
    
    interface DocumentSelector:Object
        def abstract select():bool
        def abstract get_document():string
    
    class Text:TextBuffer
        construct ( initial:string = "" )
            this.text = initial
    
    class DocumentFileSelector:Object implements DocumentSelector
    
        _parent:Window
        _uri:string = ""
    
        construct( parent:Window )
            _parent = parent
    
        def select():bool
            var dialog = new FileChooserDialog( "Open file",
                                                _parent,
                                                FileChooserAction.OPEN,
                                                dgettext( "gtk30", "_OK"),
                                                ResponseType.ACCEPT,
                                                dgettext( "gtk30", "_Cancel" ),
                                                ResponseType.CANCEL
                                               )
    
            selected:bool = false
            var response = dialog.run()
            case response
                when ResponseType.ACCEPT
                    _uri = dialog.get_uri()
                    selected = true
    
            dialog.destroy()
            return selected
    
        def get_document():string
            return "Reading the text from a URI is not implemented\n%s".printf(_uri)
    
    class Load:Object implements Command
    
        _receiver:TextBuffer
        _document_selector:DocumentSelector
    
        construct( receiver:TextBuffer, document_selector:DocumentSelector )
            _receiver = receiver
            _document_selector = document_selector
    
        def execute()
            if _document_selector.select()
                _receiver.text = _document_selector.get_document()
    

    图形用户界面的常见高级模式是model-view-controller (MVC)。这是关于解耦您的对象,以便可以轻松地重复使用和更改它们。在示例中,document 已成为表示模型的对象。通过使其成为一个单独的对象,它允许为相同的数据提供多个视图。例如,在编写 StackOverflow 问题时,您有一个编辑器窗口,但也有一个预览。两者都是相同数据的不同视图。

    在示例中,标题工具栏已使用command pattern 进一步分为不同的对象。工具栏中的每个按钮都有一个关联的命令。通过将命令作为单独的对象,可以重复使用命令。例如键绑定 Ctrl-O 也可以使用Load 命令。这样,附加到打开文档按钮的命令代码就不需要重写来附加到 Ctrl-O。

    命令模式使用接口。只要一个对象实现了execute() 方法,它就可以用作命令。 Load 命令还利用对象的接口来询问用户要打开哪个 URI。 Gtk+ 还提供了FileChooserNative。因此,如果您想切换到使用FileChooserNative 对话框而不是FileChooserDialog,您只需编写一个实现DocumentSelector 接口的新对象并将其传递给Load 命令。通过以这种方式解耦对象,可以使您的程序更加灵活,并且私有字段的使用仅限于每个对象。

    附带说明一下,在编译您的示例时有一些警告:warning: Gtk.Stock has been deprecated since 3.10。此答案中的示例使用了较新的方式:

    • 对于打开的文档图标the GNOME developer documentation for Stock Items 声明“使用命名图标“document-open”或标签“_Open”。所以我使用了document-open。这些名称来自freedesktop.org Icon Naming Specification
    • 对于文件选择器对话框中的“确定”按钮,GNOME Developer documentation 声明“不要使用图标。使用标签“_OK”。”前面的下划线表示它是由gettext 国际化和翻译的。 gettext 使用“域”,即翻译文件。对于 GTK+3,域称为gtk30。要在编译程序时启用gettext,需要将默认域的宏传递给 C 编译器。这就是需要-X -DGETTEXT_PACKAGE 的原因。同样在精灵程序中Intl.setlocale() 需要将语言环境设置为运行时环境。完成此操作后,使用 LC_ALL="zh_CN" ./text_editor_example 之类的东西来运行您的程序,如果您安装了该语言环境,则会以中文显示“确定”按钮

    【讨论】:

    • @AIThomas 这让我在 Github 上阅读的代码更加清晰!你做了这么好的合成!谢谢!
    猜你喜欢
    • 2012-12-19
    • 1970-01-01
    • 2012-02-25
    • 2010-12-22
    • 2011-03-24
    • 1970-01-01
    • 2016-03-19
    • 2012-02-02
    • 2015-12-26
    相关资源
    最近更新 更多