为什么发件人被挑出非模块?

时间:2014-07-28 15:27:00

标签: qt pyqt pyside

为什么有些人在Qt编程中使用sender()时要谨慎?典型的答案是它违反了代码模块化的原则。来自Qt documentation about pitfalls in PySide

  

如果要获取发出信号的对象,可以这样做   使用QtCore.QObject.sender(),虽然你应该三思而后行   在使用它之前(有关详细信息,请参阅QObject的API文档)。如果你   真的,真的决定你必须使用它,请注意正确   现在,你不能把它称为静态方法,而只能从一个   QObject插槽。

作为一名新的PySide程序员,我不会考虑在除了一个插槽之外的任何地方使用sender,所以第二个问题似乎没有实际意义。

第一个原因建议检查doc的API。让我们来看看。发件人From the API docs

  

这个功能违反了面向对象的模块化原则。

在Summerfield的PyQt书中也提到了这个警告,他在那里写道

  

有些程序员不喜欢使用sender()因为他们有这种感觉   不是很好的面向对象风格。

他说的全部。

与使用PyQt / PySide / Qt的其他标准做法相比,是什么使sender特别是非模块化的?在PySide中,我们不断在模型/视图框架中调用internalPointers之类的东西,或者通过使它们成为self的属性(有效地将它们视为全局变量)来隐藏在方法中使用的参数。这些和其他此类实践似乎在技术上违反了模块化,并且似乎在GUI编程中无处不在。

那么为什么人们认为sender值得特别挑剔,特别令人担忧的模块化?或者也许等价,Qt编程的模块化程度如何呢?

总的来说,sender似乎是一种非常有用,简单的方法。与partial函数或lambda表达式或QSignalMapper之类的内容相比,使用和教授其他人更容易。因此,我可以看到使用sender的好处,坦率地说很难理解将它们包含在官方Qt文档中的缺点。

1 个答案:

答案 0 :(得分:3)

一般而言,对象的插槽可以完全没有信号或任何兼容信号来调用。在前一种情况下,没有发件人这样的东西 - 例如,使用QMetaObject::invokeMethod时。当通过信号激活插槽时,发送方对象可以是任何类。你可以从sender()得到的最多的是它是一个QObject,如果它是任何东西的话。

信号槽机制设计用于解耦对象,但是依赖于sender()你正在反转它并且正在耦合到发送对象。这有一些应用程序,当设计需要这样的耦合时,例如当你实现widget hider时,或者当你想要将插槽的动作转移到相关的特定实例时宾语。在这种有限的情况之外,需要按设计进行耦合,sender()的使用表明设计和耦合不良是没有充分理由的。

文档仅仅意味着在选择将耦合引入设计之前,理解信号槽机制的目的以及解耦软件设计的好处。

Lambda表达式实际上完全消除了sender()QSignalMapper的需要,所以你在那里反对自己:

connect(foo, &Foo::aSignal, [&foo]{ foo.method(); });
// as opposed to
connect(foo, &Foo::aSignal, [&foo]{ qobject_cast<Foo*>(sender())->method(); });

// and also
QList<QPushButton*> buttons;
QPointer<QLabel> label;
for (int i = 0; i < buttons; ++i)
  connect(buttons[i], &QAbstractButton::clicked, [i, label] {
    label->setText(QString("Button %i was clicked.").arg(i+1);
  });

另一方面,QSignalMapper存在的原因无论如何都被夸大了,因为你已经拥有了可以在Qt4风格的代码中使用的属性系统。

class Helper : public QObject {
  Q_OBJECT
  QPointer<QLabel> m_label;
public:
  Helper(QLabel * label, QObject * parent = 0) : QObject(parent), m_label(label) {}
  Q_SLOT void showIndex() {
    m_label->setText(
      QString("Button %i was clicked.").arg(sender()->property("index").toInt())
    );
  }
};

void setup(QList<QAbstractButton*> buttons) {
  int i = 1;
  foreach (QAbstractButton* button, buttons) {
    button->setProperty("index", i++);
    connect(button, SIGNAL(clicked()), helper, SLOT(showIndex());
  }
}

&#34;内部&#34; QModelIndex的指针只是一种在索引中携带特定于模型的数据的方式,在设计和发布时遵循合理的约束条件:

  1. 在某些模板上使用时破坏的C ++编译器(VC6!),
  2. 链接二进制文件的大小不佳优化,进一步排除了索引类型的消费者的模板参数化,
  3. 传值值语义,没有pimpl的开销,
  4. 没有vtable指针的开销,
  5. 不要让不幸的用户暴露于object slicing问题。
  6. 它是它所设计的时代的产物。当然,它可以变得更好,而不会将internalXxx方法暴露给广大公众,但这就是为什么这些方法被称为内部首先。最终,如果你真的希望用脚射击自己,没有人会阻止你。作为索引的消费者,你应该假装那些方法不存在。这就是它的全部内容。

    是的,即使回到VC6时代,也可以使用专用的池分配器为索引和类似的类做低开销的pimpl。甚至可以利用所述池分配器来确定派生类的运行时类型,并实现虚拟析构函数机制而不需要vtable指针开销。当然。但它并没有这样做,而且决定权在我们身边。鉴于模型实施者需要大量解释来安全地利用这种&#34;魔术&#34;,我认为内部指针机制是一个更安全的赌注。