青青草原综合久久大伊人导航_色综合久久天天综合_日日噜噜夜夜狠狠久久丁香五月_热久久这里只有精品

S.l.e!ep.¢%

像打了激速一樣,以四倍的速度運轉,開心的工作
簡單、開放、平等的公司文化;尊重個性、自由與個人價值;
posts - 1098, comments - 335, trackbacks - 0, articles - 1
  C++博客 :: 首頁 :: 新隨筆 :: 聯系 :: 聚合  :: 管理

[Reference]設計Qt風格的C++API

Posted on 2008-11-19 22:47 S.l.e!ep.¢% 閱讀(605) 評論(0)  編輯 收藏 引用 所屬分類: Management

[Reference]設計Qt風格的C++API
【zz】設計Qt風格的C++API
我的手邊文章,經常隨手拿起一讀,一讀必有收獲。QT凝結了眾多軟件大家的良思妙想,關于API的設計上也有自己獨到的見解。閑言少敘,文章開始...
?
設計Qt風格的C++API
作者Matthias Ettrich,譯者Googol Lee,原文地址在這里。

在奇趣(Trolltech),為了改進Qt的開發體驗,我們做了大量的研究。這篇文章里,我打算分享一些我們的發現,以及一些我們在設計Qt4時用到的原則,并且展示如何把這些原則應用到你的代碼里。

好的API的六個特性
便利陷阱
布爾參數陷阱
靜態多態
命名的藝術
指針還是引用?
例子:QProgressBar
如何把API設計好
設計應用程序接口,API,是很難的。這是一門和設計語言同樣難的藝術。這里可以選擇太多的原則,甚至有很多原則和其他原則有矛盾。

現在,計算機科學教育把很大的力氣放在算法和數據結構上,而很少關注設計語言和框架背后的原則。這讓應用程序員完全沒有準備去面對越來越重要的任務:創造可重用的組件。

在面向對象語言普及之前,可重用的通用代碼大部分是由庫提供者寫的,而不是應用程序員。在Qt的世界里,這種狀況有了明顯的改善。在任何時候,用Qt編程就是寫新的組件。一個典型的Qt應用程序至少都會有幾個在程序中反復使用的自定義組件。一般來說,同樣的組件會成為其他應用程序的一部分。KDE,K桌面環境,走得更遠,用許多追加的庫來擴展Qt,實現了數百個附加類。(一般來說,一個類就是一個可重用組件,原文這里沒有寫清楚。)

但是,一個好的,高效的C++ API是由什么組成的呢?是好還是壞,取決于很多因素——比如,手頭的工作和特定的目標群體。好的API有很多特性,一些特性是大家都想要的,而另一些則是針對特定問題域的。

好的API的六個特性
API是面向程序員的,用來描述提供給最終用戶的GUI是什么樣子。API中的P帶表程序員(Programmer),而不是程序(Program),用來強調API是給程序員用的,給人類的程序員用的。

我們堅信API應該是最小化且完整的,擁有清晰且簡單的語義,直覺化,容易記憶,并且引導人寫出易讀的代碼。

最小化:最小化的API是指一個類盡可能只擁有最少的公開成員且盡可能只擁有最少的類。這個原則可以讓API更簡單易懂,更好記,更容易除錯,且更容易改變。
完整的:完整的API是指要提供所有期望的功能。這個可能與最小化原則相沖突。另外,如果一個成員函數屬于一個不應該屬于的類,很多潛在的使用者都會找不到這個函數。
擁有清晰且簡單的語義:就像其他設計工作一樣,你必須遵守最小驚奇原則(the principle of least surprise)。讓常見的任務簡單易行。不常見的工作可行,但不會讓用戶過分關注。解決特殊問題時,不要讓解決方案沒有必要的過度通用。(比如,Qt3中的QMimeSourceFactory可以通過調用QImageLoader來實現不同的API。)
直覺化:就像電腦上的其他東西一樣,API必須是直覺化的。不同的經驗和背景會導致在判斷什么是直覺而什么不是時不同的感覺。如果一個中級用戶不讀文檔就可以使用(a semi-experienced user gets away without reading the documentation,沒懂這里的get away該怎么翻譯),并且一個程序員不懂API就可以理解縮寫的代碼,這種API就是直覺化的。
易于記憶:讓API易于記憶,使用統一且精確的命名方法。使用可識別的模式和概念,并且避免縮寫。
引導易讀的代碼(Lead to readable code):代碼一經寫就,會讀(并且除錯和修改)多次。易讀的代碼可能會花點時間來寫,但是可以節省產品周期中的其他時間。
最后,記住,不同類型的用戶會用到API的不同部分。雖然簡單的實例化一個Qt類是非常直覺化的,讓資深專家在試圖子類化之前讀一遍文檔,是很合理的。

便利陷阱
這是個常見的誤解:更好的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");

布爾參數陷阱
布爾參數通常會導致不易讀的代碼。更進一步,給一個已經存在的函數加入一個布爾參數,這常常是個錯誤。在Qt里,一個傳統的例子是repaint(),這個函數帶有一個布爾參數,來標識是否擦除背景(默認擦除)。這讓代碼通常寫成:

??? widget->repaint(false);

初學者很容易把這句話理解成“別重畫”!

這樣做是考慮到布爾參數可以減少一個函數,避免代碼膨脹。事實上,這反而增加了代碼量。有多少Qt用戶真的記住了下面三行程序都是做什么的?

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

一個好一些的API可能看起來是這樣:

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

在Qt4里,我們重新設計了widget,使得用戶不再需要不重畫背景的重畫widget,來解決這個問題。Qt4原生支持雙緩存,廢掉了這個特性。

這里還有一些例子:

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

一個顯而易見的解決方法是,使用枚舉類型代替布爾參數。這正是我們在Qt4中QString大小寫敏感時的處理方法。比較:

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

靜態多態
相似的類應該含有相似的API。在必要的時候——就是說,需要使用運行時多態的時候——這可以通過繼承實現。但是多態依舊會發生在設計時期。比如,如果你用QListBox代替QComboBox,或者用QSlider代替QSpinBox,你會發現相似的API使這種替換非常容易。這就是我們所說的“靜態多態”。

靜態多態也使API和程序模式更容易記憶。作為結論,一組相關類使用相似的API,有時要比給每個類提供完美的單獨API,要好。

(譯注:C++ 0x將要引入的concept,就是靜態多態的語法層實現。這個要比單獨的函數名相似更強大且易用。)

命名的藝術
命名,大概是設計API時唯一最重要的問題了。該怎么稱呼這個類?成員函數該叫什么?

通用的命名規則一些規則通常對所有名字都是有用的。首先,就像我之前提到的,別用縮寫。甚至很明顯的縮寫,比如“prev”表示“previous”從長遠看也是不劃算的,因為用戶必須記住哪些詞是縮寫。

如果API本身不一致,事情自然會變得很糟糕,比如, Qt3有activatePreviousWindow()和fetchPrev()。堅持“沒有縮寫”的規則更容易創建一致的API。

另一個重要但更加微妙的規則是,在設計類的時候,必須盡力保證子類命名空間的干凈。在Qt3里,沒有很好的遵守這個規則。比如,拿QToolButton來舉例。如果你在Qt3里,對一個QToolButton調用name()、caption()、text()或者textLabel(),你希望做什么呢?你可以在Qt Designer里拿QToolButton試試:

name屬性繼承自QObject,表示一個對象用于除錯和測試的內部名字。
caption屬性繼承自QWidget,表示窗口的標題,這個標題在視覺上對QToolButton沒有任何意義,因為他們總是跟隨父窗口而創建。
text屬性繼承自QButton,一般情況下是按鈕上現實的文字,除非useTextLabel為真。
textLabel在QToolButton里聲明,并且在useTextLabel為真時顯示在按鈕上。
由于對可讀性的關注,name在Qt4里被稱作objectName,caption變成了windowsTitle,而在QToolButton里不再有單獨的textLabel屬性。

給類命名標識一組類而不是單獨給每個類找個恰當的名字。比如,Qt4里所有模式感知項目的視圖類(model-aware item view classes)都擁有-View的后綴(QListView、QTableView和QTreeView),并且對應基于項目的類都用后綴-Widget代替(QListWidget、QTableWidget和QTreeWidget)。

給枚舉類型及其值命名當聲明枚舉時,時刻記住,在C++(不像Java和C#)中,使用枚舉值不需要類型信息。下面的例子演示了給枚舉值起個太過常用的名字所引起的危害:

??? namespace Qt
{
enum Corner { TopLeft, BottomRight, ... };
enum CaseSensitivity { Insensitive, Sensitive };
...
};
tabWidget->setCornerWidget(widget, Qt::TopLeft);
str.indexOf("$(QTDIR)", Qt::Insensitive);

在最后一行,Insensitive是什么意思?一個用于命名枚舉值的指導思想是,在每個枚舉值里,至少重復一個枚舉類型名中的元素:

??? namespace Qt
{
enum Corner { TopLeftCorner, BottomRightCorner, ... };
enum CaseSensitivity { CaseInsensitive,
CaseSensitive };
...
};
tabWidget->setCornerWidget(widget, Qt::TopLeftCorner);
str.indexOf("$(QTDIR)", Qt::CaseInsensitive);

當枚舉值可以用“或”連接起來當作一個標志時,傳統的做法是將“或”的結果作為一個int保存,這不是類型安全的。Qt4提供了一個模板類 QFlags<T>來實現類型安全,其中T是個枚舉類型。為了方便使用,Qt為很多標志類名提供了typedef,所以你可以使用類型 Qt::Alignment代替QFlags<Qt::AlignmentFlag>。

為了方便,我們給枚舉類型單數的名字(這樣表示枚舉值一次只能有一個標志),而“標志”則使用復數名字。比如:

??? enum RectangleEdge { LeftEdge, RightEdge, ... };
typedef QFlags<RectangleEdge> RectangleEdges;

有些情況下,“標志“類使用了單數的名字。這時,枚舉類使用-Flag做后綴:

??? enum AlignmentFlag { AlignLeft, AlignTop, ... };
typedef QFlags<AlignmentFlag> Alignment;

(這里為啥不是把”標志“類用-Flag做后綴,而是把枚舉值做后綴呢?感覺有點混淆……)

給函數和參數命名給函數命名的一個規則是,名字要明確體現出這個函數是否有副作用。在Qt3,常數函數QString::simplifyWhiteSpace()違反了這個原則,因為它返回類一個QString實例,而不是像名字所提示的那樣,更改了調用這個函數的實例本身。在Qt4,這個函數被重命名為QString::simplified()。

參數名是程序員的重要信息來源,雖然在使用API時,并不直接展示在代碼里。由于現代IDE在程序員寫代碼時可以自動顯示參數名(就是自動感知或者自動補全之類的功能),值得花時間給頭文件里聲明的參數一個合適的名字,并且在文檔中也使用相同的名字。

給布爾值設置函數(Setter)、提取函數(Getter)和屬性命名給布爾屬性的設置函數和提取函數一個合適的名字,總是非常痛苦的。提取函數應該叫做checked()還是isChecked()?scrollBarsEnabled()還是areScrollBarEnabled()?

在Qt4里,我們使用下列規則命名提取函數:

形容類的屬性使用is-前綴。比如:
isChecked()
isDown()
isEmpty()
isMovingEnable()
另外,應用到復數名詞的形容類屬性沒有前綴:
scrollBarsEnabled(),而不是areScrollBarsEnabled()
動詞類的屬性不使用前綴,且不使用第三人稱(-s):
acceptDrops(),而不是acceptsDrops()
allColumnsShowFocus()
名詞類的屬性,通常沒有前綴:
autoCompletion(),而不是isAutoCompletion()
boundaryChecking()
有時,沒有前綴就會引起誤解,這種情況使用前綴is-:
isOpenGLAvailable(),而不是openGL()
isDialog(),而不是dialog()
(通過調用dialogue()方法,正常情況下會期望返回一個QDialog*的實例。)
設置函數名字繼承自提取函數名,只是移掉了所有前綴,并使用set-做前綴,比如:setDown()還有setScrollBarsEnabled()。屬性的名字與提取函數相同,只是去掉了前綴。

指針還是引用?
傳出參數的最佳選擇是什么,指針還是引用?

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

大部分C++書推薦在能用引用的地方就用引用,這是因為一般認為引用比指針更“安全且好用”。然而,在奇趣(Trolltech),我們傾向使用指針,因為這讓代碼更易讀。比較:

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

只有第一行能清楚的說明,在函數調用后,h、s和v將有很大幾率被改動。

例子:QProgressBar
為了展示如何實際應用這些概念,我們將學習Qt3中的API QProgressBar并和Qt4里相通的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()是緊耦合的。

改進API的關鍵,是注意到QProgressBar和Qt4的QAbstractSpinBox類及其子類QSpinBox,QSlider和QDial很相似。解決方法?用minimum、maximum和value代替progress和totalSteps。加入alueChanged()信號。加入setRange()函數。

之后觀察progressString、percentage和indicator實際都指一個東西:在進度條上顯示的文字。一般來說文字是百分比信息,但是也可以使用setIndicator()設為任意字符。下面是新的API:

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

默認的文字信息是百分比信息。文字信息可以藉由重新實現text()而改變。

在Qt3 API中,setCenterIndicator()和setIndicatorFollowStyle()是兩個影響對齊的函數。他們可以方便的由一個函數實現,setAlignment():

??? 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);
...
};

如何把API設計好(原文是How to Get APIs Right,我總想成We do APIs right……)
API需要質量保證。第一個修訂版不可能是正確的;你必須做測試。寫些用例:看看那些使用了這些API的代碼,并驗證代碼是否易讀。

其他的技巧包括讓別的人分別在有文檔和沒有文檔的情況下,使用這些API;或者為API類寫文檔(包括類的概述和獨立的函數)。

當你卡住時,寫文檔也是一種獲得好名字的方法:僅僅是嘗試把條目(類,函數,枚舉值,等等呢個)寫下來并且使用你寫的第一句話作為靈感。如果你不能找到一個精確的名字,這常常說明這個條目不應該存在。如果所有前面的事情都失敗了并且你確認這個概念的存在,發明一個新名字。畢竟,“widget”、 “event”、“focus”和“buddy”這些名字就是這么來的。

?

青青草原综合久久大伊人导航_色综合久久天天综合_日日噜噜夜夜狠狠久久丁香五月_热久久这里只有精品
  • <ins id="pjuwb"></ins>
    <blockquote id="pjuwb"><pre id="pjuwb"></pre></blockquote>
    <noscript id="pjuwb"></noscript>
          <sup id="pjuwb"><pre id="pjuwb"></pre></sup>
            <dd id="pjuwb"></dd>
            <abbr id="pjuwb"></abbr>
            在线观看国产精品淫| 久久丁香综合五月国产三级网站| 性色一区二区| 久久久av毛片精品| 国产伦理精品不卡| 亚洲女同同性videoxma| 亚洲精品护士| 久久久久久午夜| 黑人巨大精品欧美黑白配亚洲| 国内精品久久久久久| 亚洲欧美日韩精品久久奇米色影视| 欧美中文字幕久久| 久久亚洲精品中文字幕冲田杏梨| 亚洲欧美一区二区视频| 亚洲黄色av一区| 欧美成人亚洲| 久久av一区二区三区漫画| 在线一区二区视频| 久久久久久久久蜜桃| 欧美久久99| 国产一区91精品张津瑜| 亚洲精品免费在线| 欧美伊人久久久久久久久影院| 欧美在线视频免费观看| 91久久久久久久久久久久久| 欧美日韩亚洲一区三区 | 亚洲激情精品| 亚洲香蕉在线观看| 91久久黄色| 亚洲欧美春色| 欧美大片va欧美在线播放| 亚洲激情六月丁香| 99视频在线观看一区三区| 久久不见久久见免费视频1| 欧美全黄视频| 亚洲国产女人aaa毛片在线| 欧美精选一区| 国产一区二区0| 亚洲欧美美女| 亚洲国产小视频| 你懂的网址国产 欧美| 日韩视频中午一区| 欧美777四色影视在线| 国产一二精品视频| 午夜免费在线观看精品视频| 亚洲精品美女在线观看| 欧美91大片| 狂野欧美一区| 亚洲欧美福利一区二区| 欧美三级在线| avtt综合网| 亚洲黄色一区二区三区| 美女国产一区| 亚洲国产日韩欧美一区二区三区| 国产亚洲精品自拍| 国产日韩欧美亚洲一区| 国产欧美日韩高清| 亚洲视频在线观看| 一本色道久久综合精品竹菊 | 欧美日韩伦理在线免费| 在线电影院国产精品| 久久夜色精品国产| 久久精品免费观看| 国内成人精品2018免费看| 狠狠综合久久| 亚洲成人资源| 精品av久久707| 欧美在线观看一区二区三区| 一区二区三区四区五区精品视频 | 正在播放亚洲| 欧美精品亚洲精品| 日韩图片一区| 亚洲天堂av在线免费| 久久亚洲影院| 亚洲国产99精品国自产| 欧美激情二区三区| 欧美人与禽性xxxxx杂性| 亚洲一区亚洲| 午夜在线电影亚洲一区| 亚洲国产激情| 一区二区三区欧美在线观看| 国产欧美日韩综合一区在线播放| 91久久精品www人人做人人爽 | 亚洲视频一起| 午夜精品视频在线| 亚洲国产精品久久久久婷婷884| 欧美视频在线一区| 亚洲欧美日韩综合| 欧美电影打屁股sp| 久久精品国产精品亚洲综合| 黄色成人小视频| 亚洲精品美女在线| 欧美午夜宅男影院| 蜜桃久久av| 欧美日韩国产在线播放网站| 国产日韩视频一区二区三区| 久久久999精品视频| 亚洲精选久久| 亚洲成人直播| 亚洲欧美欧美一区二区三区| 欧美成人有码| 欧美亚洲在线观看| 欧美99在线视频观看| 午夜视频在线观看一区二区| 最新中文字幕一区二区三区| 欧美午夜电影在线观看| 西西人体一区二区| 欧美成人精品三级在线观看| 欧美在线视频免费| 欧美日韩1区| 麻豆乱码国产一区二区三区| 欧美性一二三区| 欧美大片免费看| 这里只有精品丝袜| 久久久国产精品一区| 日韩视频一区二区三区在线播放| 欧美中文字幕精品| 欧美日韩另类国产亚洲欧美一级| 在线亚洲欧美视频| 久久精品国产一区二区电影| 国产乱理伦片在线观看夜一区 | 亚洲精选在线观看| 午夜日韩av| 欧美日韩国产专区| 亚洲激情欧美| 亚洲电影免费观看高清| 欧美一区二区三区喷汁尤物| 欧美成人午夜激情视频| 久久av一区二区| 国产精品婷婷| 亚洲一区在线直播| 中文国产成人精品| 欧美久久久久| 欧美国产日本韩| 亚洲福利在线观看| 欧美在线www| 久久国产精品99国产精| 国产区欧美区日韩区| 亚洲欧美日本日韩| 久久成人精品一区二区三区| 国产日韩欧美三级| 久久久最新网址| 国产色产综合产在线视频| 蜜臀久久99精品久久久画质超高清 | 欧美淫片网站| 久久久久9999亚洲精品| 国产主播一区二区三区| 久久精品国产精品亚洲| 免费成人在线视频网站| 亚洲大片免费看| 欧美精品成人在线| 亚洲美女av网站| 亚洲欧美日韩国产| 国产一区二区在线观看免费| 亚洲国产精品ⅴa在线观看| 夜夜夜久久久| 性色一区二区| 久久亚洲私人国产精品va| 亚洲福利视频一区二区| 欧美日韩午夜在线| 欧美一区二区精品久久911| 蜜臀91精品一区二区三区| 国产精品日日做人人爱| 亚洲免费中文| 亚洲夜间福利| 国产真实久久| 中文网丁香综合网| 亚洲第一网站| 欧美日韩国产免费| 欧美大片在线观看| 99在线热播精品免费| 国产麻豆精品久久一二三| 久久久噜噜噜久久人人看| av成人激情| 欧美成人免费va影院高清| 亚洲一区免费在线观看| 激情成人av在线| 国产精品av免费在线观看| 久久尤物视频| 欧美亚洲一区| 亚洲理论在线观看| 免费欧美日韩国产三级电影| 99精品欧美一区二区三区 | 久久久亚洲成人| 亚洲欧美日韩国产成人| 欧美激情一区二区在线| 亚洲视频在线观看一区| 亚洲高清中文字幕| 午夜精品久久99蜜桃的功能介绍| 欧美高清视频免费观看| 欧美xxxx在线观看| 午夜精品av| 亚洲视频在线一区观看| 亚洲国产一区二区在线| 国产亚洲午夜| 国产精品免费看久久久香蕉| 欧美成人有码| 免费成人高清| 久久久久欧美| 久久xxxx精品视频|