制作 GUI 时清理 OOP 设计

Clean OOP design when making a GUI

假设我有两个主要的 类、ApplicationApplicationGUIApplication 做了很多事情并且可以在不知道 ApplicationGUI 存在的情况下快乐地 运行。 ApplicationGUI 在很多方面与 Application 相关联,它可能有 50 或 100 个不同的旋钮可以改变 Application 的行为。

ApplicationGUI 是一个层次结构,它有许多 ControlGroup 的实例,每个包含任意数量的 ButtonKnob,甚至另一个ControlGroup.

当前设计:在实例化 ApplicationGUI 时(Application 已经是 运行 一些默认参数集),我将 Application 的参数指针传递给GUI 的各种组件。例如:

 my_gui.sound_controlgroup.knob.link_to_param(&(my_application.volume));

如果我需要做一些更复杂的事情,比如调用 Applicationmy_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 组件。