API设计原则(翻译)

Posted 一只蚂蚁2

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了API设计原则(翻译)相关的知识,希望对你有一定的参考价值。

源文地址:API Design Principles

被普遍认为的一大特色就是其统一、易学、强大的API。这篇文件就是尝试着总结设计Qt风格API的一些经验。这些指导原则大部分是通用的,还有一些是约定俗成的。下面我们将通过一些现成的API进行探讨。

虽然这些指导原则主要面向公开API,但你完全可以将这些技术用来设计网络API,还可以作为礼物送你的的程序员朋友。

许你还对Jasmin Blanchette的 Little Manual of API Design (PDF)和Matthias Ettrich的 Designing Qt-Style C++ APIs 有兴趣。

优秀API的六个特征

API对于程序员就像GUI对于终端用户一样。P代表程序员(Programmer)而不是程序(Program),因此请你一定要重视一个事实:API是给程序员使用的,而他们是一群有感情的生物。

在13篇关于Qt系列API设计的文章中,Matthias提到过API应当设计得小巧、完整、简单清晰、直观、容易记忆和可读。

小巧

小巧表示一个类的公共成员尽量少。这样做可以使代码更容易理解、记忆、调试和修改。

完整

完整意味着该有的功能都应该有。这一点貌似和小巧有所冲突,但是,如果一个成员函数出现在错误的类中,那么大部分使用者应该都不会找到它。

简单清晰

正如其他的设计工作一样,你应该应用这些原则,尽量避免特殊情况。使大部分工作变得简单,特殊的部分也需要考虑但不是本文所关注的。解决特殊问题的原则:如非必要,不要将解决特殊问题的方案影响到通用的情况。(例如:Qt 3中的QMimeSourceFactory被叫做QImageLoader并且有着不同的API。)

直观

正如所有事物一样(计算机除外),API应设计的直观一些。每个人的经验和背景的有所差异,因此对直观的判断也会不同。API设计得直观的原则可以参考为:一个经验不足的使用者可以不用使用文档而仅使用代码来理解并使用API。

容易记忆

为了使API容易记忆,你应当选择一个一致并准确的命名习惯。使用容易辨别的模式和概念,避免缩写。

可读的代码

代码只会写一次,但是却会经常去阅读(调试和修改)。可读的代码也许不再需要重写,但生命周期却贯穿整个产品的生命周期。

最后请记住,不用的人会使用API的不同部分。可以简单直观地使用Qt的类,但如果需要子类化,那就应该先去阅读文档手册了。

静态多态

相似的类应当有相似的API。可以使用继承来完成这项工作,即运行时多态特性。但是多态也发生设计阶段。例如,你可以改变一个QProgressBar为QSlider,或者是一个QString为QByteArray。你会发现相识的API使得替换工作变得非常容易,因此这里我们叫它“静态多态”。

静态多态也使得记忆和编程变的更加容易。相关的类使用相似的API通常要比每个类独立的设计API要好得多。

通常,在Qt中,如果没有编译的原因,我们更倾向于使用静态多态而不是继承。这使得我们可以控制公共的类的数量,也可以使新用户可以从文档中更容易找到。

QDialogButtonBox和QMessageBox对于处理按钮有着相似的API(addButton(), setStandardButtons()等待)。但是它们并不是从某个“QAbstractButtonBox”类继承而来。

QTcpSocket和QUdpSocket都从QAbstractSocket继承。两个类使用互动完全不同。因此应该不会通常有人使用一个QAbstractSocket指针来代表使用它。

疑惑

QBoxLayout是QHBoxLayout和QVBoxLayout的基类。这样做的优点是:你可以在一个工具栏中使用QBoxLayout调用setOrientation()来设置水平还是垂直布局。但是缺点也有:在个别类中,使用者可能写下如下代码:

((QBoxLayout *)hbox)->setOrientation(Qt::Vertical);

这样做是不是有点奇怪!

基于属性的API

新版的Qt类倾向于使用“基于属性的API”。例如:

 QTimer timer;
 timer.setInterval(1000);
 timer.setSingleShot(true);
 timer.start();

通过属性,我们表示任何特性都是对象的状态的一部分,即使没有实际上没有使用Q_PROPERTY。在条件允许的情况下,使用者可以以任意的顺序设置属性,即属性是互不相关的。例如,之前的代码还可以像下面这样写:

 QTimer timer;
 timer.setSingleShot(true);
 timer.setInterval(1000);
 timer.start();

为了方面,我们还可以这样写:

timer.start(1000);

类似的,对于QRegExp,我们可以这样写:

 QRegExp regExp;
 regExp.setCaseSensitive(Qt::CaseInsensitive);
 regExp.setPattern(".");
 regExp.setPatternSyntax(Qt::WildcardSyntax);

实现这样的API的代价是我们不能懒惰地构造对象。例如QRegExp的例子,如果没有通过setPattern()设置模式为‘.’,对象就不知道模式的具体语法。

属性经常也会是级联的,这种请求处理一点要倍加小心。考虑QToolButton的“默认图标尺寸”接口“iconSize()”:

 toolButton->iconSize(); // 返回当前风格的默认尺寸
 toolButton->setStyle(otherStyle);
 toolButton->iconSize(); // 返回其他otherStyle的默认尺寸
 toolButton->setIconSize(QSize(52, 52));
 toolButton->iconSize(); // 返回 (52, 52)
 toolButton->setStyle(yetAnotherStyle);
 toolButton->iconSize(); // 返回 (52, 52)

注意一旦我们设置iconSize,它的属性将一直保持。改变当前风格不会改变iconSize。这是很棒的。有时,我们还需要重置属性,有两种途径:
• 传入一个特殊的值(例如QSize()、-1、Qt::Alignment(0))表示“重置”
• 使用一个明确的函数如resetFoo()或unsetFoo()

对于iconSize,使用QSize()(QSize(-1,-1)也一样)表示“重置”是完全足够的了。

在某些情况下,获取函数返回的值与设置函数会不一致。例如,调用widget->setEnabled(true)后widget->isEnabled()却可能返回false。因为widget的父部件可能是禁用的。这样也是OK的,因为通常我们就是需要检测它的状态(一个widget在父部件是禁用状态或本身是禁用状态,则部件应该是禁用状态,只有当父部件启用且本身都是启用状态,部件才能是启用状态)。当然这种情况应答在手册中进行说明。

C++细节

值和对象

指针和引用

对于传出参数,指针和引用谁好一点呢?

 void getHsv(int *h, int *s, int *v) const
 void getHsv(int &h, int &s, int &v) const

大部分C++书籍推荐,只要有可能,都使用引用。这条原则是引用比指针“更安全”。相反,在Qt中我们倾向于使用指针,因为这样做可以让代码可读性更强。请比较:

 color.getHsv(&h, &s, &v);
 color.getHsv(h, s, v);

只有前面的哪行代码可以清晰地表达h、s和v在函数调用的时候会被修改。

尽管如此,编译器不太喜欢传出参数,因此你应当在以后的API设计中避免使用。作为替代,你可以返回一个小巧可爱的结构体:

 struct Hsv  int hue, saturation, value ;
 Hsv getHsv() const;

传递const-ref和传递值

  • 如果类型大于16个字节,则传递const-ref。
  • 如果没有明确的拷贝构造函数和析构函数,传递const-ref可以避免执行这些方法。
  • 所有的其他情况通常应当传递值。

例子:

void setAge(int age);
void setCategory(QChar cat);
void setName(QLatin1String name);
void setAlarm(const QSharedPointer<Alarm> &alarm); // const-ref比拷贝构造函数和析构函数执行得更快
// QDate, QTime, QPoint, QPointF, QSize, QSizeF, QRect 这些类你应当传递值。

虚函数

在C++中,如一个函数在类中被定义为虚函数,它的行为允许在子类中进行覆盖。这样在的目的是可以用你的代码替换现有的调用路径。如果没有其他人调用这些函数,在你定义为虚函数时就一定要小心。

// Qt3中的的QTextEdit:这些函数没有理由定义为虚函数
C++
virtual void resetFormat();
virtual void setUndoDepth( int d );
virtual void setFormat( QTextFormat &f, int flags );
virtual void ensureCursorVisible();
virtual void placeCursor( const QPoint &pos;, QTextCursorc = 0 );
virtual void moveCursor( CursorAction action, bool select );
virtual void doKeyboardAction( KeyboardAction action );
virtual void removeSelectedText( int selNum = 0 );
virtual void removeSelection( int selNum = 0 );
virtual void setCurrentFont( const QFont &f );
virtual void setOverwriteMode( bool b ) overWrite = b;

在移植QTextEdit从Qt3到Qt4的过程中,大部分这类虚函数都被移除了。有趣(但不意外)的是,这样做并没有引起太大的抱怨。为何?因为Qt3中并没有对QTextEdit使用多态。也就是说,Qt3中并没有调用这些函数。总之,没有什么理由让你子类化QTextEdit并实现这些函数除非你自己调用了这些函数。如果你的程序在Qt之外需要多态,那你可以自己去实现它。

避免虚函数

在Qt中,我们由于各种原因控制虚函数的数量。每个虚函数通过插入不可控的节点的调用都使bug修复更加困难(通常会使输出无法预测)。在虚函数的实现代码中,人们会干一些疯狂的事情,例如:
* 发送事件
* 发出信号
* 重入事件循环(例如:打开一个模式文件对话框)
* 删除对象(也可能是造成“删除当前对象”的某些方式)

还存在很多避免过多使用虚函数的其他理由:
* 在不中断BC的情况下,你不能够增加、移动和删除虚函数
* 你不能轻易地重写一个虚函数
* 编译器通常不能优化或者内联地调用虚函数
* 调用虚函数需要查询虚函数表,这使得它需要调用正常函数的2-3倍的时间开销。
* 虚函数使得类的值拷贝变得困难(可以实现,但是会非常凌乱和让人不堪忍受)

经验告诉我们,一个没有虚函数的类的bug和维护成本更少。

一个通用的原则是:除非我们作为一个工具或是这个类的主要使用者,否则不应该使用虚函数。

Virtualness vs. copyability

多态对象和值类型的类通常不是好朋友。

当一个类有虚函数时必须定义虚析构函数以避免当基类销毁时子类没有清理数据造成的内存泄露。

如果你希望拷贝或分配类,或是通过值进行比较,你可能需要实现拷贝构造函数、赋值操作符合对于操作符。

 class CopyClass 
 public:
 CopyClass();
 CopyClass(const CopyClass &other);
 ~CopyClass();
 CopyClass &operatorh1.(const CopyClass &other);
 bool operator== (const CopyClass &other) const;
 bool operator!=(const CopyClass &other) const;

 virtual void setValue(int v);
 ;

如果你为这个类创建了一个子类,在你的代码中将开始出现不可预测的事情。正常情况下,如果没有虚函数和虚析构函数,人们不能依赖多态来创建一个子类。然而,如果你添加了虚函数或是一个虚析构函数,这些立马会变成创建子类的理由并且事情变得更加复杂。至少第一眼很容易想到你可以简单地定义虚操作。但是沿着这条路径可以也会导致混乱和破坏(叫做:不可读的代码)。研究一下下面的例子:

 class OtherClass 
 public:
 const CopyClass &instance() const; // 它返回啥?我改怎么指定它?
 ;

(此节正在施工中。。。)

const

C++提供了关键字“const”来表示不会被修改或类似的效果。这些应用在简单的值、指针或指针指向的对象和对象不能修改状态的特殊的属性函数上面。

但是请注意,const本身并没有提供足够的值,很多其他语言并没有提供“const”关键字,但也没有自动提供缺乏他们的理由。事实上,在你的C++源码中,如果你使用寻找和替换的方式来移除所有出现“const”关键字的函数,代码同样会编译并运行良好。当然,在编码目的上使用“const”还是很重要的。

让我们通过在Qt设计中的几个相关主题进行探讨使用“const”:

输入参数:const指针
带有指针输入参数的const函数通常指针参数也是const。

如果函数的确定义为const,则意味着它不会有任何副作用,也不会修改对象的是否可见状态。所以它为何会需要非const的输入参数嘛?记住const函数通常是被其他const函数调用的,因此,非const的指针是很难被传进来的(在不是有const_cast的情况下,并且只要可能,我们真的希望避免使用const_cast)。

修改前:

 bool QWidget::isVisibleTo(QWidget *ancestor) const;
 bool QWidget::isEnabledTo(QWidget *ancestor) const;
 QPoint QWidget::mapFrom(QWidget *ancestor, const QPoint &pos) const;

QWidget定义了很多输入参数是非const指针的const函数。请注意函数是允许修改widget的,但不是它自己。像这样的函数通常带有const_cast。如果使用const指针参数将会是更好的选择。

修改后:

 bool QWidget::isVisibleTo(const QWidget *ancestor) const;
 bool QWidget::isEnabledTo(const QWidget *ancestor) const;
 QPoint QWidget::mapFrom(const QWidget *ancestor, const QPoint &pos) const;

请注意:我们已在QGraphicsItem中修复了这个问题,在QWidget必须等到Qt5了:

 bool isVisibleTo(const QGraphicsItem *parent) const;
 QPointF mapFromItem (const QGraphicsItem *item, const QPointF &point) const;

返回值:const值

如果函数不是返回引用,则返回值是一个右值。

非安全的右值总是有着cv不可靠的的类型。因此即使在语法上可以给它添加一个“const”也不能使对待右值它不会改变某些东西好理解。大部分现代的编译器在遇到这种代码时都会答应一个警告。

当对一个类类型右值非const成员是函数添加一个“const”是禁止的,就像直接管理它的成员变量一样。

并不是添加一个“const”就允许访问了,但是在少数情况下在对象生命周期中也需要改变这个右值对象。这通常发生在语句结束阶段,不那么正式的说法就是,在下一个分号的时候。
例子:

 struct Foo
 
 void setValue(int v)  value = v; 
 int value;
 ;

Foo foo()
 
 return Foo();
 

const Foo cfoo()
 
 return Foo();
 

int main()
 
 // 下面的代码可以编译,foo()是不能被赋值的非const右值
 // (这里通常要求是一个左值),但是成员可以作为左值访问:
 foo().value = 1; // 可编译,但临时变量在真个语句结束时会丢失。

 // 下面的代码可以编译,foo()是一个不能被赋值的非const右值,
 // 但是调用成员函数(即使是非const)是完全OK的:
 foo().setValue(1); // 可编译,但临时变量在真个语句结束时会丢失。

 // 下面的代码不能编译,foo()是const右值,它的const成员不能不能被赋值:
 cfoo().value = 1; // 不可编译

 // 下面的代码不可编译,foo()是const右值,一个不能被非const调用的成员函数:
 cfoo().setValue(1); // 不可编译
 

返回值:指针和const指针

在对象的const函数应该返回指针还是const指针的主题上,很多人发现在C++中“const是完全正确的”是不正确的。这个问题起源于当const函数为一个成员返回一个非const指针不会修改自身的状态。返回指针这个简单的动作不会影响大对象的可见状态,也不会改变对象的职责状态。但是它却给了编程人员直接修改对象数据的的机会。

下面的例子展示了很多使用const函数返回非const指针绕过constness的方法:

 QVariant CustomWidget::inputMethodQuery(Qt::InputMethodQuery query) const
 
 moveBy(10, 10); // 无法编译!
 window()->childAt(mapTo(window(), rect().center()))->moveBy(10, 10); // 编译通过!
 

返回const指针的函数保证这层作用(也许是并不希望或没有预料的),至少在某种角度是这样的。但是那些函数你更倾向于返回一个const指针呢,或是其中的一个链表?如果我们坚持const是正确的这条观念,每个对成员返回一个指针的const函数(或是对成员的一个指针链表)必须返回一个const指针。在实践中这是很不幸的,因为将导致无法使用的API:

 QGraphicsScene scene;
 // … 在 scene 中填充

 foreach (const QGraphicsItem *item, scene.items()) 
 item->setPos(qrand() % 500, qrand() % 500); // 无法编译,item是一个const指针
 

QGraphicsScene::items()是一个const函数,这一点可能导致你觉得它更改只能返回const指针。

在Qt中我们总是仅仅使用非const的模式。我们已经选择了一个很实用的途径:相对于返回非const指针,返回const指针很可能导致过分地使用const_cast。

返回值:返回值还是返回const引用?

如果我们需要返回一个对象的拷贝,返回一个const引用是最快的途径。但是,这样会使得你在此之后不能够重构这个对象的代码。(使用术语d-pointer,我们可以在任何时候改变Qt类的内存表现;但是我们不能在不破坏二进制兼容的情况下将一个函数的标记从“const QFoo&”为“QFoo”。)因此,我们通常更愿意返回“QFoo”而不是“const QFoo &”,除非在极少对速度要求苛刻或代码重构没任何问题(例如:QList::at())的情况下。

const和对象状态

在C中关于const的正确性的讨论有很多,因为这个主题可以切入很多地方(如基于指针的函数)。

但是通常的规则是,一个const函数不能改变一个对象的可见状态。状态的含义是“主要职责”。这并不是以为者非const函数就会改变对象的私有数据成员,也不是说const函数就一定不会改变。但是这样的函数是起作用,有明显的功能的。const函数通常不会有这些作用。像下面:

QSize size=widget->sizeHint(); // const
widget->move(10, 10);   // not const

委托是将绘制搞成另外一回事。它的状态包括它的主要职责,它在此之上的对象。要求绘制的目标是有副作用的;它会改变在设备上绘制的外观(状态)。因此,paint()使用const是不合理的。此外,Interview的paint()或是QIcon的paint()使用const是合理的。没有人会在一个const函数用调用QIcon::paint(),除非他明确的想去除函数的const属性。在这种情况下,明确地使用const_cast会比较好。

// QAbstractItemDelegate::paint is const
void QAbstractItemDelegate::paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const

// QGraphicsItem::paint is not const
void QGraphicsItem::paint(QPainter& painter, const QStyleOptionGraphicsItem& option, QWidget& widget=0)

关键字const并不会为你“工作”。考虑重载const/非const函数比移除它要好。

API的含义和文档

但对函数传入-1时你该怎么做?等等。。。

警告/致命错误/等等

API也需要质量保证。第一个版本永远不会正确,你必须测试它。寻找那些地方在使用这些API并且核实一下代码的可读性。

其他的技巧包括是否有人通过手册或不通过手册使用API(包括类的概述和每个函数的说明)。

命名的艺术

在设计一个API时,命名可能是一个单独的非常重要的议题。类应该怎样被调用,函数应该被怎样调用?

通用命名规则

少数规则可用应用到所有情况的命名。首先,像我先前提及的,不要使用缩写。归根结底,即使明显的缩写(如“prev”对于“previous”)也不能有啥优势,因为用户必须记住那些单词背缩写了。

如果API不一致,事情自然就会变得糟糕。例如:Qt 3 有activatePreviousWindow()fetchPrev().严格是不使用缩写可以让创建一致性API变得更加简单。

另外一个重要但不易察觉的规则是应当保持子类的命名空间清晰。在Qt 3中,这条原则并不会总是被遵循。为了说明,我们使用QToolButton作为例子。在Qt 3中,如果你调用QToolButton的name()caption()text()textLabel(),你希望得到什么呢?在Qt Designer中使用QToolButton试一下吧:

  • name属性继承自QObject,代码对象的内部名称,可用于调试和测试。
  • caption属性继承自QWidget,表示窗口的标题,对于QToolButton来说,没有什么意义,因此它通常通过父对象创建。
  • text属性继承自QButton,通常只在按钮上使用,除非useTextLabeltrue
  • textLabel在QToolButton中定义,如果useTextLabeltrue将会在按钮上显示。

为了可读性,在Qt4中,name改为了objectName,caption编程了windowTitle,QToolButton也不在有任何textLabel属性。

当你迷惑的时候,手册也是一个很好的途径帮你发现好的命名。用你的直觉作为灵感为你的条目(类,函数,枚举等待)写文档。如果你不能发现一个准确的名字,这通常代表这个条目不应该存在。如果所有的事情都失败但你还坚信观点是合理的,那么创作一个新的名字。这样,毕竟,怎样“widget”,“event”,“focus”和“buddy”就出现了。

类命名

找到一组类的名称代替各自单独的类。例如,在Qt4中,模型视图类都已View作为后缀(QListView,QTableView,QTreeView),相关的基类也以Widget作为后缀(QListWidget,QTableWidget,QTreeWidget)。

枚举命名

当定义枚举是,我们一定要注意,在C++中(不想Java或是C#),枚举值并不会不使用类型。下面的例子将说明为枚举值使用的太通用的命名的危险:

namespace Qt

  enum CornerTopLeft,ButtonRight,...;
  enum CaseSensitivityInsensitive, Sensitive;
  ...
;

tabWidget->setConnerWidget(widget, Qt::TopLeft);
str.indexOf("$(QTDIR)",Qt::Insensitive);

最后一行代码,不敏感到底是和含义?枚举类型命名的一条知道原则是在每个枚举值名字汇总重复至少一个枚举类型的元素的名字:

namespace Qt

  enum CornerTopLeftCorner,ButtonRightCorner,...;
  enum CaseSensitivityCaseInsensitive, CaseSensitive;
  ...
;

tabWidget->setConnerWidget(widget, Qt::TopLeftCorner);
str.indexOf("$(QTDIR)",Qt::CaseInsensitive);

当没举器的值可以被或进行组合时,可以作为标记。传统的解决方案是存储每个结果或的结果为一个整型,但是这样不是类型安全的。Qt4提供了一个模板类QFlags<T>,这里的T表示枚举类型。为了方便,QT为标记类型命名提供了类型重定义,所以你可以使用Qt::Alignment代替QFlags<Qt::AlignmentFlag>。

按照惯例,我们提供单独的名称的枚举(因为它在同一时刻只能是一个值),“flags”代表名字的复数。例:

enum RectangleEdgeLeftEdge,RightEdge,...;
typedef QFlags<RectangleEdge> RectangleEdges;

在某些情况下,“flags”是一个单值,在这种情况下,枚举类型以Flag作为后缀:

enum AlignmentFlagAlignLeft, AlignTop,...;
typedef QFlags<AlignmentFlag> Alignment;

函数和参数的命名

函数命名的一条规则是,保证名字的清晰和反应函数是否有副作用,即使它没有出现在使用API的代码中。由于现在的开发环境都会在程序员写代码时的时候显示,所以为参数给定一个准确的名字是非常好的,并且在文档中也应当使用相同的名字。

为Boolean的设置,获取和属性命名

为bool属性的获取和设置函数寻找一个好的名字总是一个特别的痛楚。获取是应该叫做checked()还是isChecked()呢?是scrollBarsEnabled()还是areScrollBarEnabled()呢?

在Qt4中,我们使用以下指导原则为获取函数命名:

  • 形容词使用is作为前缀,例:

    • isChecked()
    • isDown()
    • isEmpty()
    • isMovingEnabled()
  • 然而,形容词应用在复数名词上不使用前缀:

    • scrollBarsEnabled(),不是areScrollBarsEnabled()
  • 动词不使用前缀并且比使用第三人称(-s):

    • acceptDrops(),不是acceptsDrops()
    • allColumnShowFocus()
  • 名词通常没有前缀

    • autoCompletion(),不是isAutoCompletion()
    • boundaryChecking()
  • 有时候,没有前缀是容易误导的,在这种情况是,添加is前缀:

    • isOpenGLAvailable(),不是openGL()

    • isDialog(),不是dialog()

    (在函数中调用dialog(),正确情况下我们希望返回一个QDialog。)

设置函数的命名就是在获取函数的名字上去掉前缀然后添加set前缀。例如,setDown()和setScrollBarsEnabled()。属性的命名和获取函数一样,但是要去掉前缀。

避免一些常见的陷阱

方便陷阱

常见的误解:使用API,用越少的代码完成越好。一定要注意,代码只会写一次,但是理解和阅读却要很多次,例如:

QSlider* slider = new QSlider(12,18,3,13,Qt::Vertical, 0, "volume");

比下面的的代码更难阅读(也更难编写)

QSlider* slider = new QSlider(Qt::Vertical);
slider->setRange(12,18);
slider->setPageStep(3);
slider->setValue(13);
slider->setObjectName("volume");

Boolean参数陷阱

boolean参数通常会导致难以阅读的代码。通常,还会添加bool参数到一个现有的函数中。在Qt中,一个传统的例子是repaint(),它提供一个bool参数选项指示背景是否需要擦除(默认是需要)。这将导致如下代码:

widget->repaint(false);

一个新手可能会阅读为:不要重绘!

显而易见,bool参数节省了一个函数,因此帮助减少了臃肿。可事实上,它确实在增加臃肿。有多少Qt用户真正知道下面这三行代码的含义呢?

widget->repaint();
widget->repaint(false);
widget->repaint(true);

一个稍微好一点的API可能会是这样:

widget->repaint();
widget->repaintWithoutErasing();

在Qt4中,我们通过简单地移除了不绘制widget的方法。Qt4本身支持双缓冲以淘汰这个特性。

下面是更多的例子:

widget->setSizePolicy(QSizePlicy::Fixed,QSizePolicy::Expanding,true);
textEdit->insert("Where's Waldo?",true,true,false);
QRegExp rx("moc_''''''.c??",false,true);

一个易于理解的解决方案是使用枚举类型代替bool参数,下面是我们在Qt4中QString使用大小写敏感。请比较:

str.replace("USER",user, false); // Qt 3
str.replace("USER",user, Qt::CaseInsensitive); // Qt 4

复制陷阱

专题学习

QProgressBar

在练习中展示这些方面,我们将学习研究比较Qt3和Qt4中QProgressBar的API,Qt3中:

class QProgressBar : public QWidget
 
 …
 public:
 int totalSteps() const;
 int progress() const;

 const QString &progressString() const;
 bool percentageVisible() const;
 void setPercentageVisible(bool);

 void setCenterIndicator(bool on);
 bool centerIndicator() const;

 void setIndicatorFollowsStyle(bool);
 bool indicatorFollowsStyle() const;

 public slots:
 void reset();
 virtual void setTotalSteps(int totalSteps);
 virtual void setProgress(int progress);
 void setProgress(int progress, int totalSteps);

 protected:
 virtual bool setIndicator(QString &progressStr,
 int progress,
 int totalSteps);
 …
 ;

这些API是相当的复杂和不一致。例如,对于reset(),setTotalSteps()和setProgress()之间的紧密关系一点也不清晰。

提升QProgressBar的API的关键是我们注意到QProgressBar的API和Qt4的QAbstractSpinBox及其子类如QSpinBox、QSlider、QDial非常相似。方案呢?替换progress和totalSteps为mininum、maximum和value。增加一个valueChanged()信号。增加一个setRange()的方便的函数。

下一个注意到的是progressString、percentage和indicator的确涉及到一件事:在进度条上显示的文字。通常情况下是一个百分比,但是它可以通过setIndicator()函数设置为任何值。下面是新的API:

virtual QString text() const;
 void setTextVisible(bool visible);
 bool isTextVisible() const;

默认情况下,文字显示为一个百分比指示器。可以通过重新实现text()来改变这个特性。

在Qt3中,setCenterIndicator()和setIndicatorFollowsStyle()是两个影响对齐方式的函数。他们可以更好地使用一个函数替代,setAligment():

void setAlignment(Qt::Alignment alignment);

如果编程人员没有调用setAlignment(),对齐方式将基于风格进行选取。在基于Motif风格是,文字显示在中间,其他风格则显示在右边。

下面是改善后的QProgressBar的API:

class QProgressBar : public QWidget
 
 …
 public:
 void setMinimum(int minimum);
 int minimum() const;
 void setMaximum(int maximum);
 int maximum() const;
 void setRange(int minimum, int maximum);
 int value() const;

 virtual QString text() const;
 void setTextVisible(bool visible);
 bool isTextVisible() const;
 Qt::Alignment alignment() const;
 void setAlignment(Qt::Alignment alignment);

 public slots:
 void reset();
 void setValue(int value);

 signals:
 void valueChanged(int value);
 …
 ;

QAbstractPrintDialog 和 QAbstractPageSizeDialog

Qt4看到关于两个类的幽灵:QAbstractPrintDialog and QAbstractPageSizeDialog ,它们是QPrintDialog和QPageSizeDialog的基类。这里没有任何服务目的,

以上是关于API设计原则(翻译)的主要内容,如果未能解决你的问题,请参考以下文章

( 转 ) 优秀REST风格 API的设计原则

什么是REST API

理解RESTful Api设计

报童钱包和迪米特法则(设计模式迪米特原则经典论文翻译)

翻译 | The Principles of OOD 面向对象设计原则

设计 api, url 的原则