避免在 Genie 中使用全局变量
Avoiding global variables in Genie
下面是 Genie 中 ToolbarButton 的工作代码。 objective 是获取所选文件的 uri,return 返回到 class 的 construct/init。问题是在我遇到的所有示例中都使用了全局变量(如下面的代码所示)。它看起来不直观,我担心每当代码变大时,消除错误就会变得更加困难,因为这些变量会开始累积。是否有任何其他方法可以使函数 openfile return 成为 class 的 construct/init 中的常规变量的 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 的累积吗?
首先是术语说明,然后是概括。
A "global variable" 可以在程序的任何地方访问,所以它的作用域是全局的。您在问题中提到的 _variables
是 object 范围内的私有字段。它们只能通过 object 中定义的代码访问。但是,您对 object 中私有工作变量的累积感到担忧是正确的。
设计 object 很难做到,技术和想法在几十年的实践和研究中不断发展。 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.
SOLID 中的 S 代表 Single Responsibility Principle 并且是查看示例的良好起点。通过调用您的 object、Application
并将私有工作变量视为全局变量,这表明您在单个 object 中编写整个应用程序。您可以做的是开始将 Application
分成许多不同的 object,这些 object 更专注于单一的责任领域。首先,虽然我认为我会重命名 Application
object。我去了 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()
图形用户界面的常见 high-level 模式是 model-view-controller (MVC)。这是关于 de-coupling 你的 object 的,所以它们可以很容易地 re-used 和改变。在示例中 document
已成为代表模型的 object。通过将其设为单独的 object,它允许为同一数据提供多个视图。例如,在编写 Whosebug 问题时,您有一个编辑器 window,还有一个 pre-view。两者都是对同一数据的不同看法。
在示例中,header 工具栏已使用 command pattern 进一步分为不同的 object。工具栏中的每个按钮都有一个关联的命令。通过将命令作为单独的 objects,命令可以是 re-used。例如键绑定 Ctrl-O 也可以使用 Load
命令。这样,附加到打开文档按钮的命令代码不需要 re-written 即可将其附加到 Ctrl-O.
命令模式使用接口。只要 object 实现了 execute()
方法,它就可以用作命令。 Load
命令还使用 object 的接口,询问用户打开哪个 URI。 Gtk+ 还提供了一个FileChooserNative。因此,如果您想切换到使用 FileChooserNative
对话框而不是 FileChooserDialog
,您只需要编写一个新的 object 来实现 DocumentSelector
接口并将其传递给Load
命令代替。 de-coupling object 以这种方式使您的程序更加灵活,并且私有字段的使用仅限于每个 object.
附带说明一下,在编译您的示例时出现了一些警告:warning: Gtk.Stock has been deprecated since 3.10
。此答案中的示例使用较新的方式:
- 打开文档图标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
- 对于文件选择器对话框中的“确定”按钮,GNOME Developer documentation 表示 "Do not use an icon. Use label "_OK“。”前面的下划线表示它被
gettext
国际化和翻译。 gettext
使用 'domains' 是翻译文件。对于 GTK+3,该域称为 gtk30
。要在编译程序时启用 gettext
,需要将默认域的宏传递给 C 编译器。这就是为什么需要 -X -DGETTEXT_PACKAGE
的原因。同样在 Genie 程序中,需要 Intl.setlocale()
将语言环境设置为 运行 时间环境。当使用 LC_ALL="zh_CN" ./text_editor_example
到 运行 之类的东西完成此操作时,如果您安装了该语言环境,您的程序将以中文显示“确定”按钮
下面是 Genie 中 ToolbarButton 的工作代码。 objective 是获取所选文件的 uri,return 返回到 class 的 construct/init。问题是在我遇到的所有示例中都使用了全局变量(如下面的代码所示)。它看起来不直观,我担心每当代码变大时,消除错误就会变得更加困难,因为这些变量会开始累积。是否有任何其他方法可以使函数 openfile return 成为 class 的 construct/init 中的常规变量的 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 的累积吗?
首先是术语说明,然后是概括。
A "global variable" 可以在程序的任何地方访问,所以它的作用域是全局的。您在问题中提到的 _variables
是 object 范围内的私有字段。它们只能通过 object 中定义的代码访问。但是,您对 object 中私有工作变量的累积感到担忧是正确的。
设计 object 很难做到,技术和想法在几十年的实践和研究中不断发展。 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.
SOLID 中的 S 代表 Single Responsibility Principle 并且是查看示例的良好起点。通过调用您的 object、Application
并将私有工作变量视为全局变量,这表明您在单个 object 中编写整个应用程序。您可以做的是开始将 Application
分成许多不同的 object,这些 object 更专注于单一的责任领域。首先,虽然我认为我会重命名 Application
object。我去了 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()
图形用户界面的常见 high-level 模式是 model-view-controller (MVC)。这是关于 de-coupling 你的 object 的,所以它们可以很容易地 re-used 和改变。在示例中 document
已成为代表模型的 object。通过将其设为单独的 object,它允许为同一数据提供多个视图。例如,在编写 Whosebug 问题时,您有一个编辑器 window,还有一个 pre-view。两者都是对同一数据的不同看法。
在示例中,header 工具栏已使用 command pattern 进一步分为不同的 object。工具栏中的每个按钮都有一个关联的命令。通过将命令作为单独的 objects,命令可以是 re-used。例如键绑定 Ctrl-O 也可以使用 Load
命令。这样,附加到打开文档按钮的命令代码不需要 re-written 即可将其附加到 Ctrl-O.
命令模式使用接口。只要 object 实现了 execute()
方法,它就可以用作命令。 Load
命令还使用 object 的接口,询问用户打开哪个 URI。 Gtk+ 还提供了一个FileChooserNative。因此,如果您想切换到使用 FileChooserNative
对话框而不是 FileChooserDialog
,您只需要编写一个新的 object 来实现 DocumentSelector
接口并将其传递给Load
命令代替。 de-coupling object 以这种方式使您的程序更加灵活,并且私有字段的使用仅限于每个 object.
附带说明一下,在编译您的示例时出现了一些警告:warning: Gtk.Stock has been deprecated since 3.10
。此答案中的示例使用较新的方式:
- 打开文档图标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 - 对于文件选择器对话框中的“确定”按钮,GNOME Developer documentation 表示 "Do not use an icon. Use label "_OK“。”前面的下划线表示它被
gettext
国际化和翻译。gettext
使用 'domains' 是翻译文件。对于 GTK+3,该域称为gtk30
。要在编译程序时启用gettext
,需要将默认域的宏传递给 C 编译器。这就是为什么需要-X -DGETTEXT_PACKAGE
的原因。同样在 Genie 程序中,需要Intl.setlocale()
将语言环境设置为 运行 时间环境。当使用LC_ALL="zh_CN" ./text_editor_example
到 运行 之类的东西完成此操作时,如果您安装了该语言环境,您的程序将以中文显示“确定”按钮