Как избежать глобальных переменных в Genie

Ниже приведен рабочий код ToolbarButton в Genie. Цель состоит в том, чтобы получить uri для выбранного файла и вернуть его обратно в конструкцию/инициализацию класса. Проблема в том, что во всех примерах, которые я встречал, используются глобальные _variables (как показано в коде ниже). Это выглядит неинтуитивно, и я боюсь, что когда код станет больше, будет сложнее удалять ошибки, так как эти переменные начнут накапливаться. Есть ли другой способ заставить функцию openfile вернуть uri в обычную переменную в конструкции/инициализации класса?

Вот код:

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?


person lf_araujo    schedule 10.04.2016    source источник


Ответы (1)


Сначала замечание по терминологии, а затем обобщение.

Доступ к «глобальной переменной» можно получить в любом месте вашей программы, поэтому ее область действия является глобальной. _variables, на которые вы ссылаетесь в своем вопросе, являются частными полями в рамках вашего объекта. Доступ к ним возможен только с помощью кода, определенного в этом объекте. Однако вы правы, беспокоясь о накоплении частных рабочих переменных внутри ваших объектов.

Проектировать объекты сложно, а методы и идеи развивались в течение нескольких десятилетий практики и исследований. Аббревиатура SOLID, введенная Майклом Фезерсом, резюмирует пять принципов объектного ориентированный дизайн, который обеспечивает полезные критерии для оценки вашего дизайна. Также книга Шаблоны проектирования: элементы многоразового объектно-ориентированного программного обеспечения, написанная Gamma et al. и впервые опубликованная в 1994 году, дает хорошее обобщение и классификацию проектов в объектно-ориентированном программировании. В этой книге редактор документов используется в качестве учебного примера для демонстрации использования таких шаблонов. И принципы SOLID, и шаблоны проектирования в книге являются абстракциями, они не объясняют вам, как писать программу, но дают набор общих идей, которые позволяют программистам обсуждать и оценивать. Поэтому я буду использовать оба этих инструмента в своем ответе, но имейте в виду, что в последние годы были разработаны дополнительные методы для дальнейшего улучшения процесса разработки программного обеспечения, в частности разработка через тестирование и разработка через поведение.

S в SOLID означает принцип единой ответственности и является хорошей отправной точкой для рассмотрения вашего примера. . Вызывая свой объект 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()

Обычный высокоуровневый шаблон для графических пользовательских интерфейсов: контроллер представления (MVC). Речь идет о разъединении ваших объектов, чтобы их можно было легко повторно использовать и изменять. В примере document стал объектом, представляющим модель. Сделав это отдельным объектом, можно получить несколько представлений одних и тех же данных. Например, при написании вопроса StackOverflow у вас есть окно редактора, а также предварительный просмотр. Оба являются разными представлениями одних и тех же данных.

В этом примере панель инструментов заголовка была дополнительно разделена на разные объекты с помощью шаблона команды. Каждая кнопка на панели инструментов имеет связанную с ней команду. Имея команды как отдельные объекты, команду можно использовать повторно. Например, привязка клавиш Ctrl-O также может использовать команду Load. Таким образом, код команды, прикрепленной к кнопке открытия документа, не нужно переписывать, чтобы привязать ее к Ctrl-O.

Шаблон команды использует интерфейс. Пока объект реализует метод execute(), его можно использовать как команду. Команда Load также использует интерфейс для объекта, который запрашивает у пользователя, какой URI открыть. Gtk+ также предоставляет FileChooserNative. Поэтому, если вы хотите переключиться на использование диалогового окна FileChooserNative вместо FileChooserDialog, вам просто нужно написать новый объект, реализующий интерфейс DocumentSelector, и вместо этого передать его команде Load. Такое разъединение объектов делает вашу программу более гибкой, а использование приватных полей ограничивается каждым объектом.

Кстати, при компиляции вашего примера было несколько предупреждений: warning: Gtk.Stock has been deprecated since 3.10. В примере в этом ответе используется более новый способ:

  • для значка открытого документа документация разработчика GNOME для Stock Items указано "Использовать именованный значок "document-open" или метку "_Open"." Так что я использовал document-open. Эти имена взяты из Спецификации именования значков freedesktop.org
  • для кнопки OK в диалоговом окне выбора файла Документация для разработчиков GNOME гласит: "Не используйте значок. Используйте метку "_OK"." Подчеркивание перед означает, что оно интернационализировано и переведено как gettext. gettext использует «домены», которые являются файлами перевода. Для GTK+3 домен называется gtk30. Чтобы включить gettext, когда ваша программа скомпилирована, макрос для домена по умолчанию должен быть передан компилятору C. Вот почему -X -DGETTEXT_PACKAGE необходим. Также в Genie программа Intl.setlocale() необходима для установки локали для среды выполнения. Когда это будет сделано с использованием чего-то вроде LC_ALL="zh_CN" ./text_editor_example для запуска вашей программы, отобразится кнопка «ОК» на китайском языке, если у вас установлена ​​эта локаль.
person AlThomas    schedule 23.04.2016
comment
@AIThomas, это делает намного более понятным код, который я читал на Github! Вы сделали такой прекрасный синтез! Благодарю вас! - person lf_araujo; 24.04.2016