Avoiding global variables in Genie

佐手、 提交于 2019-11-28 12:15:14

问题


Below is a working code of a ToolbarButton in Genie. The objective is to get the uri for the chosen file and return it back to the construct/init of the class. The problem is that in all examples I’ve come across global _variables are used (as shown in the code below). It looks unintuitive and I fear that whenever the code gets larger, it will become more difficult to remove bugs, since these variables will start to accumulate. Is any other way of making the function openfile return the uri to a regular variable within the construct/init of the class?

Here is the code:

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 ()

Or shouldn't I worry at all about _variables accumulating?


回答1:


First a note on terminology and then a generalisation.

A "global variable" can be accessed any where in your program, so its scope is global. The _variables you are referring to in your question are private fields within the scope of your object. They can only be accessed by code that is defined in that object. You are, however, right to be concerned about the accumulation of private working variables within your objects.

Designing objects is hard to do and techniques and ideas have evolved over several decades of practise and research. The SOLID acronym, introduced by Michael Feathers, sums up five principles for object oriented design that provide useful criteria for evaluating your design. Also the book, Design Patterns: Elements of Reusable Object-Oriented Software, by Gamma et al. and first published in 1994, provides a good summary and categorisation of designs in object oriented programming. That book uses a document editor as a case study for demonstrating the use of such patterns. Both the SOLID principles and the design patterns in the book are abstractions, they won't tell you how to write a program but they do give a set of common ideas that allows programmers to discuss and evaluate. So I will use both of those tools in my answer, but be aware in recent years additional techniques have been developed to further enhance the software development process, specifically test driven development and behaviour driven development.

The S in SOLID stands for the Single Responsibility Principle and is a good starting point for looking at your example. By calling your object, Application, and thinking of the private working variables as global variables then it suggests you are writing the whole application within a single object. What you can do is start to separate Application in to a number of different objects that focus more on a single area of responsibility. First though I thought I would rename the Application object. I went for EditorWindow. In my example below EditorWindow also has a Header and a DocumentView.

Compile the code below with:

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

The use of -X -DGETTEXT_PACKAGE is explained at the end of this answer.

[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()

A common high-level pattern for graphical user interfaces is model-view-controller (MVC). This is about de-coupling your objects so they can be easily re-used and changed. In the example document has become the object that represents the model. By making this a separate object it allows multiple views to be given of the same data. For example when writing a StackOverflow question you have an editor window, but also a pre-view. Both are different views of the same data.

In the example the header toolbar has been further separated into different objects using the command pattern. Each button in the toolbar has an associated command. By having the commands as separate objects the command can be re-used. For example the key binding Ctrl-O may also use the Load command. This way the code for the command attached to the open document button doesn't need to be re-written for attaching it to Ctrl-O.

The command pattern makes use of an interface. As long as an object implements the execute() method then it can be used as a command. The Load command also makes use of an interface for the object that asks the user which URI to open. Gtk+ also provides a FileChooserNative. So if you wanted to switch to using a FileChooserNative dialog instead of a FileChooserDialog you would just need to write a new object that implements the DocumentSelector interface and pass that to the Load command instead. By de-coupling objects in this way it makes your program much more flexible and the use of private fields are kept confined to each object.

As a side note, when compiling your example there were a few warnings: warning: Gtk.Stock has been deprecated since 3.10. The example in this answer uses the newer way:

  • for the open document icon the GNOME developer documentation for Stock Items states "Use named icon "document-open" or the label "_Open"." So I've used document-open. These names are from the freedesktop.org Icon Naming Specification
  • for the OK button in the file chooser dialog the GNOME Developer documentation states "Do not use an icon. Use label "_OK"." The underscore before means it is internationalised and translated by gettext. gettext uses 'domains' which are translation files. For GTK+3 the domain is called gtk30. To enable gettext when your program is compiled a macro for the default domain needs to be passed to the C compiler. This is why the -X -DGETTEXT_PACKAGE is needed. Also in the Genie program Intl.setlocale() is needed to set the locale to the runtime environment. When this is done using something like LC_ALL="zh_CN" ./text_editor_example to run your program will show the OK button in Chinese if you have that locale installed


来源:https://stackoverflow.com/questions/36526346/avoiding-global-variables-in-genie

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!