前端设计模式 - 树对象结构与订阅发布者模式 Unity与Qt【极简版】
树本身很好的体现了对象的层级与局部性,在此基础上很容易应用各种设计模式
树结构中,每个节点(TreeNode)可同时作为发布者和订阅者:
- 订阅者:节点可以订阅其他节点(通常是父节点或祖先节点)的特定事件(如状态变更、数据更新)。
- 发布者:节点状态变化时,向所有订阅者(通常是子节点或后代节点)发布事件,触发响应逻辑。
通过这种双向通信机制,树节点无需硬编码依赖彼此的引用,只需通过事件接口交互,降低耦合度。
Qt 如何体现我们的设计原则
通过订阅-发布者模式,树结构的节点通信从“硬依赖”转变为“松耦合”,尤其适合复杂UI组件(如树形控件、组织架构图)或数据结构(如AST语法树)的设计。、
[!question] 为什么父不订阅子对象信号而直接调用或委托
- 父对象对子对象本就有一种隐式的依赖,
直接调用或者委托(强引用)可以减少消息&事件带来的性能损失,同时简单调用链可直接追踪。
对于需要编译时语言通常如此,对于运行时语言就不一定了,一般也是用的信号传递,因为父对象可能也不预先知道他有几个子对象,无法强引用- 子对象不确定他属于哪个父对象或者爷爷对象,所以需要使用
消息&事件保持解耦。同时对于非局部要向外扩展的消息可以利用事件系统广播
Qt 中的两种通信方式
1. 直接调用方法(父对子)
// 父对象直接调用子对象的方法
QWidget* parent = new QWidget;
QPushButton* button = new QPushButton("Click me", parent); // 父子关系
// 父对象直接调用子的方法 - 不需要信号槽
button->setText("New Text");
button->setEnabled(false);
button->move(100, 100);
2. 信号槽机制(子对父/跨对象)
// 子对象通过信号通知父对象(不确定的监听者)
class MyWidget : public QWidget {
Q_OBJECT
public:
MyWidget() {
QPushButton* button = new QPushButton("Click me", this);
// 父对象订阅子的信号
connect(button, &QPushButton::clicked, this, &MyWidget::onButtonClicked);
}
private slots:
void onButtonClicked() {
// 响应子对象的信号
qDebug() << "Button was clicked!";
}
};
Qt 如何体现我们的设计原则
原则1:父直接调用子(性能+精确控制)
// 父窗口直接操作子控件
QMainWindow* window = new QMainWindow;
QStatusBar* statusBar = new QStatusBar(window);
// 直接调用 - 高效、精确
statusBar->showMessage("Loading...");
statusBar->setSizeGripEnabled(true);
// 布局管理也是直接调用
QVBoxLayout* layout = new QVBoxLayout;
layout->addWidget(new QPushButton("OK"));
layout->addWidget(new QPushButton("Cancel"));
原则2:子通过信号通知父(解耦)
// 按钮不知道谁会响应它的点击
// 可能是直接父级、祖父级、甚至是完全无关的对象
QPushButton* button = new QPushButton;
// 多个可能的监听者
connect(button, &QPushButton::clicked, dialog, &QDialog::accept);
connect(button, &QPushButton::clicked, logger, &Logger::logButtonClick);
connect(button, &QPushButton::clicked, analytics, &Analytics::trackEvent);
Unity 中的通信方式与设计原则
Unity 的设计哲学与 Qt 类似,但具体实现机制不同。让我展示 Unity 中如何体现同样的设计原则:
Unity 中的两种通信方式
1. 直接调用方法(父对子)
// 父对象直接调用子对象的方法
public class ParentController : MonoBehaviour
{
private ChildComponent child;
void Start()
{
// 获取子对象的引用
child = GetComponentInChildren<ChildComponent>();
// 或者通过Transform查找
// child = transform.Find("ChildObject").GetComponent<ChildComponent>();
}
void Update()
{
// 父对象直接调用子的方法
child.UpdatePosition(new Vector3(10, 0, 0));
child.SetEnabled(true);
child.ChangeColor(Color.red);
}
}
public class ChildComponent : MonoBehaviour
{
public void UpdatePosition(Vector3 newPos)
{
transform.position = newPos;
}
public void SetEnabled(bool enabled)
{
gameObject.SetActive(enabled);
}
public void ChangeColor(Color color)
{
GetComponent<Renderer>().material.color = color;
}
}
2. 事件系统(子对父/跨对象)
// 子对象通过事件通知父对象
public class ButtonController : MonoBehaviour
{
// 定义事件
public event System.Action OnButtonClicked;
public event System.Action<int> OnValueChanged;
void Update()
{
if (Input.GetMouseButtonDown(0))
{
// 触发事件,不知道谁会监听
OnButtonClicked?.Invoke();
}
}
public void SetValue(int value)
{
// 触发值变化事件
OnValueChanged?.Invoke(value);
}
}
public class UIManager : MonoBehaviour
{
void Start()
{
// 父对象订阅子对象的事件
ButtonController button = FindObjectOfType<ButtonController>();
button.OnButtonClicked += HandleButtonClick;
button.OnValueChanged += HandleValueChange;
}
void HandleButtonClick()
{
Debug.Log("Button was clicked!");
}
void HandleValueChange(int newValue)
{
UpdateUI(newValue);
}
}
Unity 如何体现设计原则
原则1:父直接调用子(性能+精确控制)
// 父对象直接操作子对象
public class GameManager : MonoBehaviour
{
public PlayerController player;
public EnemySpawner spawner;
public UIScoreDisplay scoreDisplay;
void StartGame()
{
// 直接调用 - 高效、精确控制
player.ResetPosition();
player.SetHealth(100);
spawner.StartSpawning();
scoreDisplay.ResetScore();
}
void EndGame()
{
// 直接停止所有子系统
spawner.StopAllCoroutines();
player.DisableControls();
scoreDisplay.ShowGameOver();
}
}
原则2:子通过事件通知父(解耦)
// 子对象不知道谁会监听它的事件
public class HealthComponent : MonoBehaviour
{
public event System.Action<float> OnHealthChanged;
public event System.Action OnDeath;
private float currentHealth = 100;
public void TakeDamage(float damage)
{
currentHealth -= damage;
OnHealthChanged?.Invoke(currentHealth);
if (currentHealth <= 0)
{
OnDeath?.Invoke();
}
}
}
// 多个可能的监听者
public class PlayerUI : MonoBehaviour
{
void Start()
{
HealthComponent health = GetComponent<HealthComponent>();
health.OnHealthChanged += UpdateHealthBar;
}
void UpdateHealthBar(float health) { /* 更新血条UI */ }
}
public class AchievementSystem : MonoBehaviour
{
void Start()
{
HealthComponent health = FindObjectOfType<Player>().GetComponent<HealthComponent>();
health.OnDeath += UnlockDeathAchievement;
}
void UnlockDeathAchievement() { /* 解锁成就 */ }
}
public class SoundManager : MonoBehaviour
{
void Start()
{
HealthComponent health = FindObjectOfType<Player>().GetComponent<HealthComponent>();
health.OnHealthChanged += PlayHurtSound;
}
void PlayHurtSound(float health) { /* 播放受伤音效 */ }
}
信号与槽 = 触发器与事件委托(函数对象容器)

浙公网安备 33010602011771号