Как избежать глобальных переменных в Genie
Ниже приведен рабочий код панели инструментов в Genie. Цель состоит в том, чтобы получить URI для выбранного файла и вернуть его обратно в конструкцию /init класса. Проблема в том, что во всех примерах, с которыми я сталкивался, используются глобальные переменные _ (как показано в коде ниже). Это выглядит не интуитивно, и я боюсь, что всякий раз, когда код становится больше, удалять ошибки становится все труднее, так как эти переменные начнут накапливаться. Есть ли другой способ заставить функцию 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 ()
Или я не должен беспокоиться о накоплении переменных?
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
стал объектом, который представляет модель. Делая это отдельным объектом, он позволяет нескольким представлениям получить одни и те же данные. Например, при написании вопроса Stackru у вас есть окно редактора, а также предварительный просмотр. Оба являются разными взглядами на одни и те же данные.
В этом примере панель инструментов заголовка была дополнительно разделена на различные объекты с помощью шаблона команды. Каждая кнопка на панели инструментов имеет соответствующую команду. Имея команды как отдельные объекты, команда может быть использована повторно. Например, связывание клавиш 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 Stock указано "Использовать именованный значок"document-open"или метку"_Open"." Итак, я использовал
document-open
, Эти имена взяты из спецификации именования иконок freedesktop.org. - для кнопки "ОК" в диалоговом окне выбора файлов в документации разработчика GNOME говорится "Не используйте значок. Используйте метку"_OK"". Подчеркивание перед означает, что оно интернационализировано и переведено
gettext
,gettext
использует "домены", которые являются файлами перевода. Для GTK+3 домен называетсяgtk30
, Включитьgettext
когда ваша программа скомпилирована, макрос для домена по умолчанию должен быть передан компилятору C. Вот почему-X -DGETTEXT_PACKAGE
нужно. Также в программе GenieIntl.setlocale()
необходим для установки локали в среду выполнения. Когда это сделано, используя что-то вродеLC_ALL="zh_CN" ./text_editor_example
для запуска вашей программы будет отображаться кнопка ОК на китайском языке, если у вас установлена эта локаль