C++ 的API 设计指导
原文地址:API Design Principles /wiki/API-Design-Principles
摘要:
此文为Qt 上的API设计(for C++)指导准则,其中有不少原则具有普遍适用性,整个篇幅中有很多示例,是Qt在API设计上的实践。
正文:
Qt 一致、易掌握、强大的API是它的众多著名的优点之一。此文总结了我们在设计Qt风格的API所积累的做法。其中许多准则是通用的;而其他内容更偏向与约定,遵守它主要是为了与已有的API保持一致。
虽然这些准则主要用于公共API, 你也可以在设计私有API时使用它们,把它作为与其他开发者之间的约定。
1.好API的6个特点(Six Characteristics of Good API)
API对与程序员的关系就如同GUI与用户的关系。API中的’P’实际上指的是’程序员’,而不是’程序’,它强调了所有API都是由程序员使用的事实。
Matthias 在他的Qt Quarterly 13 article about API design 中说他相信API应该很少并且是完整的,有清晰简单的语义,可凭直觉知道的,容易记忆而且能产生可读的代码。
数量最小化(Be minimal)
数量最小化的API拥有尽可能少的公有类,每个公有类的公有成员也很少。依次可使理解,记忆,调试,改变API更容易。
功能完整(Be complete)
完整的API包含了所有需要用到的功能。这和API的数量最小化有点冲突。另外,如果某个成员函数放到了错误的类里,需要使用此方法的用户就会不到它。
清晰简单的语义(Have clear and simple semantics)
如同其他的设计,我们应该遵守意外最少的原则(principle of least surprise),把常用的操
作变得容易,很少用的操作不应该很显眼。不要把没必要通用的解决方案普遍化。例如,在Qt 3 中,QMimeSourceFactory 不应被命名为 QImageLoader。
直觉性强(Be intuitive)
API 应该有较强的直觉性,就像计算机里的其他内容,。不同的履历和背景导致了不同人有不同直觉。如果一个经验不很丰富的用户没有阅读文档就能搞懂某个API,明白此API构成的代码,说明此API直觉性较强。
容易记忆( Be easy to memorize)
为使API易于记忆,应选择一个具有一致性和精确性的命名约定。使用易于识别的模式和概念,避免使用缩写。
能写出可读代码(Lead to readable code)
代码只写一次,但是却被浏览,调试,改变很多次。可读性好的代码可能需要更长的时间来写,却能在产品的整个生命周期中节省时间。
最后,记住不同的用户使用不同部分的API。尽管记住简单得使用一个Qt类的示例更直接,最好还是告诉用户在使用API时阅读相关一下文档。
2.静态多态(Static Polymorphism)
相似的类应该有相似的API。运行时多态通过继承体系实现。然而多态也发生在设计阶段。例如,如果你用QProgressBar替换QSlider,或者是QString替换QByteArray,你会发现API的简洁性使替换如此容易。此所谓“静态多态”。
静态多态也使记忆API和编程模式更加容易。因此,一组有相似接口的相关类比每个类都有自己的一套接口更好。
总体上讲,在Qt中,比起没有强有力的理由实现的继承关系,我们跟喜欢依赖静态多态。这种做法可确保公有类较少,使刚学习Qt的用户认清路线。
好的静态多态
QDialogButtonBox与 QMessageBox 在按钮操作(addButton(), setStandardButtons()等等 )有相似的API,而没有继承某个”QAbstractButtonBox”的类。
糟糕的静态多态
QTcpSocket 与 QUdpSocket 都继承了 QAbstractSocket ,它们是两个行为非常不同的类。似乎没有什么人曾经或会去使用一个QAbstractSocket指针。
不能确定的静态多态
QBoxLayout 是 QHBoxLayout 与 QVBoxLayout 的基类,好处:可以在工具栏中使用QBoxLayout调用setOrientation() 使其变为水平/垂直。坏处:需要一个额外的类,并且有可能导致用户写出这样没什么意义的代码,((QBoxLayout *)hbox)->setOrientation(Qt::Vertical)。
3. 基于特性的API(Property-Based APIs )
新的Qt类倾向于基于特性的API,例如:
[cpp] view plaincopy
1. QTimer timer;
2. timer.setInterval(1000);
3. timer.setSingleShot(true);
4. timer.start();
特性,是指此对象的任何属性——无论它是否是Q_PROPERTY。在实践中,用户应能够以任何顺序设置这些特性,也就是说,这些特性应该是正交的。例如,之前的代码可以写成:
[cpp] view plaincopy
1. QTimer timer;
2. timer.setSingleShot(true);
3. timer.setInterval(1000);
4. timer.start();
【译者注:正交特性:改变某个特性而不会影响到其他的特性。《程序员修炼之道》中讲了一个关于正交性的直升飞机坠毁的例子。】
方便起见,也写成:
[cpp] view plaincopy
1. timer.start(1000);
类似地,对于QRegExp,
[html] view plaincopy
1. QRegExp regExp;
2. regExp.setCaseSensitive(Qt::CaseInsensitive);
3. regExp.setPattern("***.*");
4. regExp.setPatternSyntax(Qt::WildcardSyntax);
为实现这种类型的API,最好不要过早构建目标。例如,在QRegExp的例子中,在不知道模式语法的时候,在setPattern()中编译"***.*"为时过早。
各种特性经常关联在一起。在上面的例子中,我们必须小心。思考一下当前风格提供的“默认的图标尺寸”和QToolButton的”iconSize”属性:
[cpp] view plaincopy
api设计1. toolButton->iconSize(); // returns the default for the current style
2. toolButton->setStyle(otherStyle);
3. toolButton->iconSize(); // returns the default for otherStyle
4. toolButton->setIconSize(QSize(52, 52));
5. toolButton->iconSize(); // returns (52, 52)
6. toolButton->setStyle(yetAnotherStyle);
7. toolButton->iconSize(); // returns (52, 52)
需要提醒的是,一旦设置了 iconSize,它就一直保持设置状态;改变当前的风格不会改变此事。这样很好。有时候,重置某个特性也很有用,有两种方法:
(1)传入一个特殊值 (如 QSize(), -1, 或者 Qt::Alignment(0)) ,意味着重置
(2)提供一个明确的重置接口,如 resetFoo() 和 unsetFoo() 对于iconSize,使用Qsize()就足够了。
在某些情况下,getters 返回的结果与设置的可能不同。例如,如果调用widget->setEnabled(true),如果它的parent处于disabled状态,widget->isEnabled()仍然返回 false。这样是可行的,正是我们想要的(widget的父widget处于disabled状态,此widget也应该变为灰,就好象也处于disabled状态一样,但是它会记得,其本身并没有处于disabled状态,正等待它的父widget变为enabled.),但是诸如这样的特性必须被详细列入文档。
4.C++ API 规范(C++ Specifics)
指针与引用(Pointers vs. References)
最好的输出参数的类型是什么,指针还是引用?
[cpp] view plaincopy
1. void getHsv(int *h, int *s, int *v) const
2. void getHsv(int &h, int &s, int &v) const
大多数C++书籍基于引用比指针“安全和优雅”的观点,推荐尽可能使用引用。相比之下,我们在开发Qt时更喜欢指针,因为使用指针可使用户代码可读性更好。比较下面两个例子:
[cpp] view plaincopy
1. &Hsv(&h, &s, &v);
2. &Hsv(h, s, v);
仅有第一行代码充分显示h,s,v 很可能被此函数调用修改。
虚函数(Virtual Functions)
虚函数(Virtual Functions)
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论