制作 GUI 时清理 OOP 设计
Clean OOP design when making a GUI
假设我有两个主要的 类、Application
和 ApplicationGUI
。 Application
做了很多事情并且可以在不知道 ApplicationGUI
存在的情况下快乐地 运行。 ApplicationGUI
在很多方面与 Application
相关联,它可能有 50 或 100 个不同的旋钮可以改变 Application
的行为。
ApplicationGUI
是一个层次结构,它有许多 ControlGroup
的实例,每个包含任意数量的 Button
和 Knob
,甚至另一个ControlGroup
.
当前设计:在实例化 ApplicationGUI
时(Application
已经是 运行 一些默认参数集),我将 Application
的参数指针传递给GUI 的各种组件。例如:
my_gui.sound_controlgroup.knob.link_to_param(&(my_application.volume));
如果我需要做一些更复杂的事情,比如调用 Application
、my_application.update_something()
的成员函数,这是怎么做到的?
简单的答案是将指向 my_application 的指针传递给 my_gui.sound_controlgroup.knob
,但是如果我只需要调用 my_application 的函数之一,那么我似乎给我的旋钮一个选项,可以更改它甚至应该知道的各种事情(例如 my_application.update_something_unrelated()
)。在这种情况下,最干净的做法是什么?
此外,这需要制作 ApplicationGUI
public 的所有子组件,或者在层次结构的每个阶段都有一个函数将指针转发到底层。这导致相当多的功能。这是UI有很多旋钮的必然结果吗?
快速简短回答
为了实现非 GUI 相关 Application
对象和 GUIApplication
对象之间的交互,我建议应用 "Property and Method and Event Handler" 范例。
扩展复杂答案
G.U.I。开发是最实用的实现之一 O.O.P。理论。
什么是 "Property and Method and Event Handler" 范式?
这意味着非 GUI 类 和 GUI 类 的构建应具有:
- 属性
- 方法
- 事件处理程序
"Events"(Handlers)也叫"Signals",用函数指针实现。不确定,但是,我认为您的 "knob" (s) 就像事件处理程序。
这是一种应用 my_application.update_something_unrelated()
的技术,你在你的问题中有。
由于 C++ 与 Java 一样没有 属性 语法,您可以使用 "getter" 和 "setter" 方法,或者使用 "property"模板。
例如,如果您的应用程序有一个 Close
方法,您可以声明类似于以下示例的内容。
注意:它们不是完整的程序,只是一个想法:
// Applications.hpp
public class BaseApplicationClass
{
// ...
};
public class BaseApplicationClientClass
{
// ...
};
typedef
void (BaseApplicationClientClass::*CloseFunctor)
(BaseApplicationClass App);
public class ApplicationClass: public BaseApplicationClass
{
// ...
public:
Vector<BaseApplicationClientClass::CloseFunctor>
BeforeCloseEventHandlers;
Vector<BaseApplicationClientClass::CloseFunctor>
AfterCloseEventHandlers;
protected:
void ConfirmedClose();
public:
virtual void Close();
} Application;
// Applications.cpp
void ApplicationClass::ConfirmedClose()
{
// do close app. without releasing from memory yet.
} // void ApplicationClass::ConfirmedClose()
void ApplicationClass::Close()
{
// Execute all handlers in "BeforeCloseEventaHandlers"
this.ConfirmedClose();
// Execute all handlers in "AfterCloseEventaHandlers"
} // void ApplicationClass::Close()
// AppShells.cpp
public class AppShell: public BaseApplicationClientClass
{
// ...
};
void AppShell::CloseHandler(ApplicationClass App)
{
// close GUI
} // void AppShell.CloseHandler(ApplicationClass App)
void AppShell::setApp(ApplicationClass App)
{
App->BeforeCloseEventHandlers->add(&this.CloseHandler);
} // void AppShell.setApp(ApplicationClass App)
void main (...)
{
ApplicationClass* AppKernel = new ApplicationClass();
ApplicationGUIClass* AppShell = new ApplicationGUIClass();
AppShell.setApp(App);
// this executes "App->Run();"
AppShell->Run();
free AppShell();
free AppKernel();
}
更新:修复了从全局函数指针 (a.k.a."global functor") 到对象函数指针 (a.k.a."method functor").
的类型声明
干杯。
您知道模型-视图-控制器 (MVC) 范例吗?将 Application
class 视为模型,将 GUI 控件的整个层次结构视为视图,将 ApplicationGUI
class 视为控制器。你不希望 Application
知道控件,你也不希望控件知道 Application
;他们都应该只与控制器对话,ApplicationGUI
.
使用 ApplicationGUI
作为控件和 Application
之间的通信管道意味着您可以通过用模拟对象替换另一个来测试 Application
或控件。更重要的是,您可以更改控件或 Application
而不会影响另一个。单独的控件不需要知道任何有关 Application
的信息——它们只需要知道在值发生变化时将其发送到哪里。 Application
不应该关心输入是来自旋钮、滑块还是文本字段。将这两个区域分开将简化它们中的每一个。
Additionally, this either requires making all subcomponents of
ApplicationGUI public or having a function at each stage of the
hierarchy to forward that pointer to the bottom level. This leads to
quite a lot of functions. Is this a necessary consequence of a UI with
a lot of knobs?
给定的控件不应该关心它管理什么值。它不需要知道该值是否决定了屏幕上外星入侵者的数量或核反应堆中的冷却液水平。它确实需要知道诸如最小值和最大值、要显示的标签、要使用的比例(线性、对数等)以及其他直接影响控件工作方式的事情。它还需要知道当事情发生变化时该告诉谁,并且它可能需要某种方式来识别自己。
考虑到这一点,ApplicationGUI
不需要为 Application
的每个可能参数公开访问器。相反,它应该有一个通用的方法让控件向它发送更新。当控件更改时,它应该向 ApplicationGUI
发送一条消息,其中包含新值及其标识符,并且 ApplicationGUI
负责将该标识符映射到 Application
的某个特定参数.控件的标识符可以是提供给它的一些标识号,也可以只是指向控件的指针。
当然,有时通信也必须以其他方式进行...GUI 通常同时具有输入和输出,因此您需要一些方法让 ApplicationGUI
获得从 Application
更新并更新 GUI 的状态。出于与上述相同的原因,Application
应该将这些更新发送给 ApplicationGUI
并让后者找到需要更改的实际 UI 组件。
假设我有两个主要的 类、Application
和 ApplicationGUI
。 Application
做了很多事情并且可以在不知道 ApplicationGUI
存在的情况下快乐地 运行。 ApplicationGUI
在很多方面与 Application
相关联,它可能有 50 或 100 个不同的旋钮可以改变 Application
的行为。
ApplicationGUI
是一个层次结构,它有许多 ControlGroup
的实例,每个包含任意数量的 Button
和 Knob
,甚至另一个ControlGroup
.
当前设计:在实例化 ApplicationGUI
时(Application
已经是 运行 一些默认参数集),我将 Application
的参数指针传递给GUI 的各种组件。例如:
my_gui.sound_controlgroup.knob.link_to_param(&(my_application.volume));
如果我需要做一些更复杂的事情,比如调用 Application
、my_application.update_something()
的成员函数,这是怎么做到的?
简单的答案是将指向 my_application 的指针传递给 my_gui.sound_controlgroup.knob
,但是如果我只需要调用 my_application 的函数之一,那么我似乎给我的旋钮一个选项,可以更改它甚至应该知道的各种事情(例如 my_application.update_something_unrelated()
)。在这种情况下,最干净的做法是什么?
此外,这需要制作 ApplicationGUI
public 的所有子组件,或者在层次结构的每个阶段都有一个函数将指针转发到底层。这导致相当多的功能。这是UI有很多旋钮的必然结果吗?
快速简短回答
为了实现非 GUI 相关 Application
对象和 GUIApplication
对象之间的交互,我建议应用 "Property and Method and Event Handler" 范例。
扩展复杂答案
G.U.I。开发是最实用的实现之一 O.O.P。理论。
什么是 "Property and Method and Event Handler" 范式?
这意味着非 GUI 类 和 GUI 类 的构建应具有:
- 属性
- 方法
- 事件处理程序
"Events"(Handlers)也叫"Signals",用函数指针实现。不确定,但是,我认为您的 "knob" (s) 就像事件处理程序。
这是一种应用 my_application.update_something_unrelated()
的技术,你在你的问题中有。
由于 C++ 与 Java 一样没有 属性 语法,您可以使用 "getter" 和 "setter" 方法,或者使用 "property"模板。
例如,如果您的应用程序有一个 Close
方法,您可以声明类似于以下示例的内容。
注意:它们不是完整的程序,只是一个想法:
// Applications.hpp
public class BaseApplicationClass
{
// ...
};
public class BaseApplicationClientClass
{
// ...
};
typedef
void (BaseApplicationClientClass::*CloseFunctor)
(BaseApplicationClass App);
public class ApplicationClass: public BaseApplicationClass
{
// ...
public:
Vector<BaseApplicationClientClass::CloseFunctor>
BeforeCloseEventHandlers;
Vector<BaseApplicationClientClass::CloseFunctor>
AfterCloseEventHandlers;
protected:
void ConfirmedClose();
public:
virtual void Close();
} Application;
// Applications.cpp
void ApplicationClass::ConfirmedClose()
{
// do close app. without releasing from memory yet.
} // void ApplicationClass::ConfirmedClose()
void ApplicationClass::Close()
{
// Execute all handlers in "BeforeCloseEventaHandlers"
this.ConfirmedClose();
// Execute all handlers in "AfterCloseEventaHandlers"
} // void ApplicationClass::Close()
// AppShells.cpp
public class AppShell: public BaseApplicationClientClass
{
// ...
};
void AppShell::CloseHandler(ApplicationClass App)
{
// close GUI
} // void AppShell.CloseHandler(ApplicationClass App)
void AppShell::setApp(ApplicationClass App)
{
App->BeforeCloseEventHandlers->add(&this.CloseHandler);
} // void AppShell.setApp(ApplicationClass App)
void main (...)
{
ApplicationClass* AppKernel = new ApplicationClass();
ApplicationGUIClass* AppShell = new ApplicationGUIClass();
AppShell.setApp(App);
// this executes "App->Run();"
AppShell->Run();
free AppShell();
free AppKernel();
}
更新:修复了从全局函数指针 (a.k.a."global functor") 到对象函数指针 (a.k.a."method functor").
的类型声明干杯。
您知道模型-视图-控制器 (MVC) 范例吗?将 Application
class 视为模型,将 GUI 控件的整个层次结构视为视图,将 ApplicationGUI
class 视为控制器。你不希望 Application
知道控件,你也不希望控件知道 Application
;他们都应该只与控制器对话,ApplicationGUI
.
使用 ApplicationGUI
作为控件和 Application
之间的通信管道意味着您可以通过用模拟对象替换另一个来测试 Application
或控件。更重要的是,您可以更改控件或 Application
而不会影响另一个。单独的控件不需要知道任何有关 Application
的信息——它们只需要知道在值发生变化时将其发送到哪里。 Application
不应该关心输入是来自旋钮、滑块还是文本字段。将这两个区域分开将简化它们中的每一个。
Additionally, this either requires making all subcomponents of ApplicationGUI public or having a function at each stage of the hierarchy to forward that pointer to the bottom level. This leads to quite a lot of functions. Is this a necessary consequence of a UI with a lot of knobs?
给定的控件不应该关心它管理什么值。它不需要知道该值是否决定了屏幕上外星入侵者的数量或核反应堆中的冷却液水平。它确实需要知道诸如最小值和最大值、要显示的标签、要使用的比例(线性、对数等)以及其他直接影响控件工作方式的事情。它还需要知道当事情发生变化时该告诉谁,并且它可能需要某种方式来识别自己。
考虑到这一点,ApplicationGUI
不需要为 Application
的每个可能参数公开访问器。相反,它应该有一个通用的方法让控件向它发送更新。当控件更改时,它应该向 ApplicationGUI
发送一条消息,其中包含新值及其标识符,并且 ApplicationGUI
负责将该标识符映射到 Application
的某个特定参数.控件的标识符可以是提供给它的一些标识号,也可以只是指向控件的指针。
当然,有时通信也必须以其他方式进行...GUI 通常同时具有输入和输出,因此您需要一些方法让 ApplicationGUI
获得从 Application
更新并更新 GUI 的状态。出于与上述相同的原因,Application
应该将这些更新发送给 ApplicationGUI
并让后者找到需要更改的实际 UI 组件。