在制作GUI时清理OOP设计

Clean OOP design when making a GUI

本文关键字:OOP 设计 GUI      更新时间:2023-10-16

假设我有两个主要类,ApplicationApplicationGUIApplication做了很多事情,可以在不知道ApplicationGUI存在的情况下快乐地运行。ApplicationGUIApplication有很多联系,它可能有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_applications的一个函数,那么我似乎给了我的旋钮一个选项,可以更改它应该知道的所有事情(例如my_application.update_something_unrelated())。在这种情况下,最干净的做法是什么?

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

快速简短回答

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

扩展复杂答案

G.U.I.的发展是O.O.p.理论最实际的实施之一。

什么是"属性和方法以及事件处理程序"范式?

这意味着构建,无论是非GUI类还是GUI类,都应该具有:

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

"事件"(处理程序)也称为"信号",用函数指针实现。不确定,但是,我认为你的"旋钮"就像事件处理程序。

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

由于C++和Java一样没有属性语法,所以可以使用"getter"answers"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();
}

UPDATE:修复了从全局函数指针(也称为"全局函子")到对象函数指针(又称为"方法函子")的类型声明。

干杯。

你知道模型-视图-控制器(MVC)范式吗?将Application类视为模型,将GUI控件的整个层次视为视图,将ApplicationGUI类视为控制器。您不希望Application了解控件,也不希望控件了解Application;它们都应当仅与控制器ApplicationGUI进行通信。

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

此外,这要么需要将ApplicationGUI公共或在层次结构,将指针转发到底层。这导致相当多的功能。这是带有很多旋钮?

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

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

当然,有时沟通也必须走另一条路。。。GUI通常既有输入又有输出,因此ApplicationGUI需要一些方法来从Application获取更新并更新GUI的状态。出于与上述相同的原因,Application应该将这些更新发送到ApplicationGUI,并让后者找到需要更改的实际UI组件。