【问题标题】:Clean OOP design when making a GUI制作 GUI 时干净的 OOP 设计
【发布时间】:2015-04-11 16:15:48
【问题描述】:

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

ApplicationGUI 是一个层次结构,因此它有许多ControlGroup 实例,每个实例包含任意数量的Buttons 和Knobs,甚至是另一个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 的所有子组件公开,要么在层次结构的每个阶段都有一个函数来将该指针转发到底层。这导致了相当多的功能。这是带有很多旋钮的 UI 的必然结果吗?

【问题讨论】:

  • 你有你的model,你有你的view ...在这里我非常想念像controller这样的中介(在它的一个化身中)。
  • 嗯...好的。我熟悉控制器的概念。我一直在使用 JUCE 进行开发,它将控制器和视图视为一回事,我想这就是我从未想到过的原因。
  • @Chet Altought 在 GUI 控件中分离 MVC 等概念是一项非常强大的技术,我建议首先设计简单的控件,然后添加将视图与控制器分离的控件。
  • @umlcat 你是否建议我坚持使用我帖子中的上述选项(大量函数和传递指向复杂对象的指针)?
  • @Chet 不完全是。我之前的评论是关于不急于 View Controller 分离的。查看下面我的答案,以获得更多建议。

标签: c++ user-interface oop


【解决方案1】:

快速简短回答

为了实现非 GUI 相关的 Application 对象和 GUIApplication 对象之间的交互,我建议应用“属性、方法和事件处理程序”范例。

扩展的复杂答案

G.U.I.开发是 O.O.P. 最实际的实现之一。理论。

什么是“Property and Method and Event Handler”范式?

这意味着构建,非 GUI 类和 GUI 类都应该有:

  • 属性
  • 方法
  • 事件处理程序

“事件”(处理程序)也称为“信号”,并通过函数指针实现。不确定,但是,我认为您的“旋钮”就像事件处理程序。

这是一种应用my_application.update_something_unrelated() 的技术,您有问题。

由于 C++ 与 Java 一样,没有属性语法,因此您可以使用“getter”和“setter”方法,或者使用“属性”模板。

例如,如果您的应用程序有一个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();
}

更新:修复了从全局函数指针(又名“全局函子”)到对象函数指针(又名“方法函子”)的类型声明。

干杯。

【讨论】:

  • 我非常喜欢这个策略(它适用于我当前的架构)。它实际上与我考虑的事情没有什么不同。我遇到的主要困难是传递函数指针。我知道如何传递非成员函数,但不知道如何在不显式传递我想要调用它的实例的情况下传递成员函数。 stackoverflow.com/questions/18145874/… 你是不是用这一行“伪编码”了这个细节:App->BeforeCloseEventHandlers->add(&this.CloseHandler); ?
  • 确实,我得到'指向成员函数错误的非常量指针'
  • @Chet 你是对的。检查函子类型声明升级。
  • 使用 (Class::Function) 而不是 (Class::*Function) 是否允许您绕过“指向成员函数的非常量指针”错误?我已经更新了我的代码以使用“回调对象”,一个包含函数指针和指向应该调用它的实例的私有指针的类。当我有空闲时间时,我会试一试。
  • @Chet 对。我忘记了* 运算符。
【解决方案2】:

您了解模型-视图-控制器 (MVC) 范例吗?将Application 类视为模型,将GUI 控件的整个层次结构视为视图,将ApplicationGUI 类视为控制器。你不想让Application知道控件,你也不想让控件知道Application;他们都应该只与控制器交谈,ApplicationGUI

使用ApplicationGUI 作为控件和Application 之间通信的管道意味着您可以测试Application 或控件,例如用模拟对象替换另一个。更重要的是,您可以更改控件或 Application 而不会影响另一个。单个控件不需要知道任何关于Application 的信息——他们只需要知道当它发生变化时将它们的值发送到哪里。 Application 不应该关心输入是来自旋钮、滑块还是文本字段。将这两个区域分开将简化它们。

此外,这要么需要将所有子组件 ApplicationGUI 公开或在每个阶段都有一个功能 层次结构将该指针转发到底层。这将导致 相当多的功能。这是 UI 的必然结果吗? 很多旋钮?

一个给定的控件不应该关心它管理什么值。它不需要知道该值是否决定了屏幕上外星入侵者的数量或核反应堆中的冷却剂液位。它确实需要知道诸如最小值和最大值、要显示的标签、要使用的比例(线性、对数等)以及其他直接影响控件工作方式的事情。它还需要知道当某些事情发生变化时该告诉谁,并且它可能需要某种方式来识别自己。

考虑到这一点,ApplicationGUI 不需要为Application 的每个可能参数公开访问器。相反,它应该有一个通用方法,让控件向它发送更新。当控件更改时,它应该向ApplicationGUI 发送一条包含新值及其标识符的消息,ApplicationGUI 负责将该标识符映射到Application 的某个特定参数。控件的标识符可以是赋予它的某个标识号,也可以只是指向控件的指针。

当然,有时通信也必须采用另一种方式... GUI 通常同时具有输入和输出,因此您需要一些方法让 ApplicationGUIApplication 获取更新并更新状态的图形用户界面。出于上述相同的原因,Application 应该将这些更新发送给ApplicationGUI,让后者找到需要更改的实际 UI 组件。

【讨论】:

    猜你喜欢
    • 2016-10-29
    • 1970-01-01
    • 2015-02-13
    • 2021-11-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-06-12
    • 2011-12-25
    相关资源
    最近更新 更多