API 分很多種 style,各有優缺跟取捨。

  • C style
  • Object Oriented C++ style
  • Template style
  • Data driven style

這種分類方式是以 C/C++ 系列區分,但我覺得在其他語言方面也有類似概念。

C style

一般 C function 們,通常會用 struct 跟一些命名規則來區別不同功能跟 component 等等。

類似的東西:PHP 那些 mysql_ 開頭的 function 們。

讓 C++ 可以 call C API

有時候使用者是用 C++ 開發,而 C style 的 API 希望能給這樣的使用者使用。在此狀況下,C style API 需要:

  1. C API 可以用 C++ compiler 編過
  2. extern "C" 處理 C++ 與 C 之間 linkage 的問題
    同樣的 function 在 C compiler 跟 C++ 的 compiler 產生的 object file 中會以不同方式呈現,例如 C++ 有 name decoration,但 C 沒有。因此 C API 得用 extern “C” 包起來,讓 C++ compiler 知道這段應該要用 C style 的 link 方式。
1
2
3
4
5
6
7
8
9
#ifdef __cplusplus
extern "C" {
#endif

// C API declaration

#ifdef __cplusplus
}
#endif

實例:C++ uses C library example

Object Oriented C++ style

以物件導向為基礎的寫法。

類似的東西:PHP 的 mysqli 物件。

Template style

以 template 於 compile time 達到 static polymorphism──相同 interface 可支援多種 type,例如 STL。

相對物件導向的多型是在 runtime 做,使用 template 有較高的執行效率,但 compile 出來的檔案會比較大。因此,相對 code 大小較著重執行效能時可用 template,反之可用物件導向。

缺點之一是 template 的定義得放在 header,導致使用的程式需一起 compile template 的 header,但仍有些方式可以將 template 定義藏在 .cpp。另外,template 的 compile error 訊息很複雜,無論自己開發有什麼問題或者使用者使用時有何問題,複雜的 error message 讓人蠻頭大的。被害過…但至今還是沒搞懂…(欸)

Data driven style

interface 提供非常 general 的 function,實際上做什麼事由 input 決定。interface 大概像這樣:

Result DoCommand(string cmd, ArgList args)

Command() 內部 implement 會依據 cmd 決定要做什麼,args 則是對應 command 的參數,Result 則是回傳值。

例如 web service,使用者丟想做的命令跟參數給 web service,web service 再依據命令及參數回應。

由於 interface 非常 general,需要相對應的 general 的參數型態來傳這些參數,像上面的 Result。在不強調 data type 的語言(weakly type language)像 python 中很好處理,但在 C++ 裡就要自己寫或用其他 library 提供的了,如 Qt 的 QVariant。

好處是 API 修改非常彈性,幾乎不需要動到 interface。壞處是單從 interface 看不出 API 提供的功能,因為太 general 了,這時候只能靠文件。

C compiler 編出來的 object file 可以跟 C++ compiler 編出來的 object file link 在一起。

假設 Linux 環境下有一個 object file foo.o 是 C compiler 編出來的,可以將 foo.o 看作一 C library,而 main.cpp 是由 C++ 實作並且使用 foo.o 裡的 function,因此 main.cpp 會 include 內含 function declaration 的 foo.h。C++ compiler 在一般狀況下會將 foo.h 內的 function 當作 C++ function 以 C++ 的規則處理。

由於 foo.o 是 C compiler 編出來的,其對 function 的處理方式不像 C++ 會額外加修飾,使得 link 時 main.ofoo.o 中 symbol 對不起來而產生 undefined reference 錯誤。在 foo.h 中加上 extern "C" 是告訴 C++ compiler 要把這段 function declaration 當作 C function 處理,也就是不做 name decoration,之後 link symbol 才對得起來。實例如下:

foo.h
1
2
3
4
5
6
7
8
9
10
// foo.h
#ifdef __cplusplus
extern "C" {
#endif

void foo();

#ifdef __cplusplus
}
#endif
foo.c
1
2
3
4
5
6
7
8
// foo.c
#include <stdio.h>
#include "foo.h"

void foo()
{
printf("C foo()!\n");
}
main.cpp
1
2
3
4
5
6
7
8
9
10
// main.cpp
#include <iostream>
#include "foo.h"

int main()
{
std::cout << "C++ main" << endl;
foo();
return 0;
}

foo.o 的 symbol:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
> gcc -c foo.c
> readelf -s foo.o

Symbol table '.symtab' contains 11 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 00000000 0 NOTYPE LOCAL DEFAULT UND
1: 00000000 0 FILE LOCAL DEFAULT ABS foo.c
2: 00000000 0 SECTION LOCAL DEFAULT 1
3: 00000000 0 SECTION LOCAL DEFAULT 3
4: 00000000 0 SECTION LOCAL DEFAULT 4
5: 00000000 0 SECTION LOCAL DEFAULT 5
6: 00000000 0 SECTION LOCAL DEFAULT 7
7: 00000000 0 SECTION LOCAL DEFAULT 8
8: 00000000 0 SECTION LOCAL DEFAULT 6
9: 00000000 20 FUNC GLOBAL DEFAULT 1 foo
10: 00000000 0 NOTYPE GLOBAL DEFAULT UND puts

一般狀況的 main.o symbol(重點在 18):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
> g++ -c main.cpp
> readelf -s main.o

Symbol table '.symtab' contains 23 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 00000000 0 NOTYPE LOCAL DEFAULT UND
1: 00000000 0 FILE LOCAL DEFAULT ABS main.cpp
2: 00000000 0 SECTION LOCAL DEFAULT 1
3: 00000000 0 SECTION LOCAL DEFAULT 3
4: 00000000 0 SECTION LOCAL DEFAULT 4
5: 00000000 1 OBJECT LOCAL DEFAULT 4 _ZStL8__ioinit
6: 00000000 0 SECTION LOCAL DEFAULT 5
7: 00000039 64 FUNC LOCAL DEFAULT 1 _Z41__static_initializati
8: 00000079 28 FUNC LOCAL DEFAULT 1 _GLOBAL__sub_I_main
9: 00000000 0 SECTION LOCAL DEFAULT 6
10: 00000000 0 SECTION LOCAL DEFAULT 9
11: 00000000 0 SECTION LOCAL DEFAULT 10
12: 00000000 0 SECTION LOCAL DEFAULT 8
13: 00000000 57 FUNC GLOBAL DEFAULT 1 main
14: 00000000 0 NOTYPE GLOBAL DEFAULT UND _ZSt4cout
15: 00000000 0 NOTYPE GLOBAL DEFAULT UND _ZStlsISt11char_traitsIcE
16: 00000000 0 NOTYPE GLOBAL DEFAULT UND _ZSt4endlIcSt11char_trait
17: 00000000 0 NOTYPE GLOBAL DEFAULT UND _ZNSolsEPFRSoS_E
18: 00000000 0 NOTYPE GLOBAL DEFAULT UND _Z3foov
19: 00000000 0 NOTYPE GLOBAL DEFAULT UND _ZNSt8ios_base4InitC1Ev
20: 00000000 0 NOTYPE GLOBAL DEFAULT UND _ZNSt8ios_base4InitD1Ev
21: 00000000 0 NOTYPE GLOBAL DEFAULT UND __dso_handle
22: 00000000 0 NOTYPE GLOBAL DEFAULT UND __cxa_atexit

foo.hextern "C"main.o symbol:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
>readelf -s main.o

Symbol table '.symtab' contains 23 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 00000000 0 NOTYPE LOCAL DEFAULT UND
1: 00000000 0 FILE LOCAL DEFAULT ABS main.cpp
2: 00000000 0 SECTION LOCAL DEFAULT 1
3: 00000000 0 SECTION LOCAL DEFAULT 3
4: 00000000 0 SECTION LOCAL DEFAULT 4
5: 00000000 1 OBJECT LOCAL DEFAULT 4 _ZStL8__ioinit
6: 00000000 0 SECTION LOCAL DEFAULT 5
7: 00000039 64 FUNC LOCAL DEFAULT 1 _Z41__static_initializati
8: 00000079 28 FUNC LOCAL DEFAULT 1 _GLOBAL__sub_I_main
9: 00000000 0 SECTION LOCAL DEFAULT 6
10: 00000000 0 SECTION LOCAL DEFAULT 9
11: 00000000 0 SECTION LOCAL DEFAULT 10
12: 00000000 0 SECTION LOCAL DEFAULT 8
13: 00000000 57 FUNC GLOBAL DEFAULT 1 main
14: 00000000 0 NOTYPE GLOBAL DEFAULT UND _ZSt4cout
15: 00000000 0 NOTYPE GLOBAL DEFAULT UND _ZStlsISt11char_traitsIcE
16: 00000000 0 NOTYPE GLOBAL DEFAULT UND _ZSt4endlIcSt11char_trait
17: 00000000 0 NOTYPE GLOBAL DEFAULT UND _ZNSolsEPFRSoS_E
18: 00000000 0 NOTYPE GLOBAL DEFAULT UND foo
19: 00000000 0 NOTYPE GLOBAL DEFAULT UND _ZNSt8ios_base4InitC1Ev
20: 00000000 0 NOTYPE GLOBAL DEFAULT UND _ZNSt8ios_base4InitD1Ev
21: 00000000 0 NOTYPE GLOBAL DEFAULT UND __dso_handle
22: 00000000 0 NOTYPE GLOBAL DEFAULT UND __cxa_atexit

盡量不要讓看過的書像船過水無痕,嘗試用自己的話做小結。

這章的重點在:What are the basic qualities of a good API?

Model the problem domain

API 是用來解決某個問題的,問題可大可小。

API 要能對問題提供抽象化的概念,並且能夠將這抽象概念以 interface 表達出來。以 C++ 來說,我的理解是用 class name 以及 function name 表達抽象概念,也就是使用 API 的人應該要能從 class 及 function 看出抽象概念及使用邏輯。

一個問題沒有絕對正確的抽象化方式,重點是 API 要保有某種一致性及邏輯。

如果用物件導向來做 modeling 就會用 object modeling,也就是會定出 object、每個 object 做什麼、object 之間的關係與互動。一個 class 應該要定義為「做什麼」(what)而不是「怎麼做」(how)。

Hide implementation details

隱藏所有實作細節。如果沒藏好,以後要改實作,使用者可能跟著改到崩潰然後就不想用你的 API 了。(無誤)

interface 訂得好,底下怎麼搞都沒關係,改 interface 比較會影響到使用者。

隱藏實作細節的方法:

  • Declaration & Definition
    盡量在 header 只寫 declaration,implementation 放在 cpp。
  • Encapsulation
    • 請把 member 藏起來,外面需要它們的話使用 getter & setter。
    • 隱藏 class 內部才使用的 implementation method 們,API 使用者不需要知道 API 實際上如何實作出功能的。
    • 隱藏 class 內部才使用的 implementation class 們。
    • 所謂隱藏就是設成 private。

PS:這裡說的使用者是指使用 API 的人,通常是其他 programmer 或自己,不是使用軟體的一般使用者。

Minimally complete

API 功能要完整,可以滿足使用者需要的所有功能,要盡可能小但無法再更小了。

要小心 virtual function 可能會公開過多 function 給使用者。不過我還沒很懂這意思…

Easy to use

看到這裡總覺得在軟體設計上到處都有這句話,只是層次不太一樣。在 application 層級上,easy to use 希望的是一般使用者的好用。而在 API 層級上則是針對 API 使用者──通常是 programmer。

  • Discoverable
    使用者光看 API 就能自己心領神會、找出如何使用。
  • Difficult to misuse
    難以誤用,舉例:傳三個 enum Year、Month、Day 當參數比傳三個 int 不容易誤用。《Effective C++》好像說過同樣的話…
  • Consistent
    API 設計的一致性
  • Orthogonal
    • method 之間沒 side effect,例如 call 改變某屬性的 method 不會動到其他屬性。
    • 修改 API 的部分實作也不會影響到其他部分。
    • 做法:保持一種資訊只有一個地方有,不要到處 copy-paste code。盡量封裝,避免一個變數到處都可以 access 以致改了 A 可能就動到 B 的行為。
  • Resource allocation
    • 用 smart pointer 管理 memory。
    • 其他種類的 resource 也可以用 class 加以管理,object 的 construct 是 allocate resource,destruct 則是 release resource,通常稱為 RAII(Resource Acquisition Is Initialization)。舉例:Qt 的 QMutexLocker。
  • Platform independent
    避免在公開的 header 用針對特定平台的 #if#ifdef,例如 #ifdef _WIN32

鬆散耦合

跟 OO 的原則一樣,class 之間、module 之間不要黏太緊。

簡單看兩個 component A 跟 B 黏得緊不緊的方式是看改了 A 後 B 會不會改很多、A 看得到 B 多少東西(如只看得到 public function 還是也看得到 private member 等等)。

要避免兩個 component 互相依賴變成 dependency cycle,不然想用 A 就一定要有 B,但邏輯上可能根本不需要 B,看起來就很怪。

這邊提兩種鬆散耦合的方法:Manager class 跟事件通知。

Manager class

Manager class 會擁有並管理某些 class,例如與 output 有關的 class。

如果有一個 Manager class 管理 N 個 output class,其他 M 個 需要使用 output class 的 class 可以不需要跟所有 output class 都有關係(這樣會有 N * M 個關聯),大家可以只跟 Manager class 有關係(只剩 N + M 個關聯),從而降低 outpu class 與其他 class 間的耦合。

原來 Manager class 還有這種功能…

事件通知

在「某件事發生時需要通知其他人」的情境下,有以下幾種方式可以降低通知者與被通知者的耦合:

  • callback
    C 寫法,使用 function pointer,也可以有很多 callback。
  • observer
    Observer Pattern
  • notification
    不針對特定事件,比較像整個系統的機制,例如 signal-slot。

這些方法可以讓通知方與被通知方彼此不需要知道對方是誰,否則雙方得知道對方是誰才能通知,而當兩邊各有很多 class 的時候,那根本是場災難……

相關 note

Murmur

老覺得跟 OO 的原則很類似,很多概念都有在其他地方看過。不過現在再看,似乎更能配合實際經驗加以理解。

像 naming、argument 順序、standard pattern 的使用、memory model、exception 的使用、error handling 等等,如果設計上有一致性,使用者較容易使用。保有一致性時,使用者可以輕易透過原本的理解認知推測新功能或其他部分如何使用。

在設計 C++ API 上,如果跟 standard library 保有相同的模式與規則也會讓 API 易於使用,畢竟大家都會用 standard library 啊!

雖然這篇標題是說 API 的設計,但小至 class 跟 function 也是有類似的觀念啊~

舉個 argument 不一致的例子:fscanf()fgets(),它們分別長這樣:

int fscanf(FILE* stream, const char* format, ...);
char* fgets(char* str, int num, FILE* stream);

兩個類似的 function,FILE* stream 參數卻是一個在第一個、一個在最後一個,讓我太久沒用就得回頭瞄一下文件……

API 的功能可以是比較 atomic、比較基本的,讓外部使用這些基本功能組合出它想要的東西。也可以是 API 本身即組合一些基本功能、提供較方便的功能給外部使用。這兩種特性雖然互相衝突,但都是作為 API 希望滿足的。

滿足這兩種特性的方式是將僅提供基本功能的 core API 及較方便的 API 分開。可能是以 class 或檔案區隔,甚至直接拆成不同 library。重點是較方便的 API 是使用 core API 的 public interface,就像在 core API 外再包一層,而不會 access 到 core API 的內部結構。

如此使用者就能有方便使用的 API 也能在想自行組合功能的時候使用進階的 core API。這概念類似於 UI 設計上常有的「進階」按鈕──平常設定頁只會有基本設定,按進階按鈕後才會顯示更細部的設定。

常用功能的 plugin 及快捷鍵:

  • 游標停在字上的時候會自動 highlight 同樣的字
    開啟 vim 後輸入 z/,目前還沒找到怎麼一開 vim 就啟動自動 highlight。
  • 跳到游標所在 variable 或 function 的宣告
  • 在 .cpp 跟 .h 之間切換
    a.vim (:A)
  • 跳到游標所在的 function 的定義
    cscope (ctrl+\ g)
    ctags (g] or ctrl+])
  • 跳到游標所在的 variable local 宣告
    ctags (gd)
  • 跳到游標所在的 variable global 宣告
    ctags (gD)
  • 全 project search
    cscope (ctrl+\ t)
  • 列出 file 中有哪些 function
    taglist (F8)
  • 自動補齊
    於 insert mode ctrl + n
  • 自動補括號
  • hex mode
    :%! xxd
  • 切換 history
    ctrl + i & ctrl + o
  • 多行註解
    ESCctrl + V 選範圍、大寫 I、輸入註解符號、ESC
  • 多行取消註解
    ESCctrl + V 選範圍、delete

My .vimrchttps://github.com/cjwind/dotfiles/blob/master/vimrc

ctags taglist

$ sudo apt-get install ctags

切到 project 資料夾產生 tag 資訊:

$ ctags --extra=+f -R *

加上 --extra=+f 可以在 vim 中使用 :tag <filename> 跳到該檔案,:tag <filename> 後再 ctrl + t 可回原本檔案。

http://www.vim.org/scripts/script.php?script_id=273taglist_45.zip,解開後將 taglist.vim 放到 ~/vim/plugin

~/.vimrc 加入相關設定:

1
2
3
4
5
6
let Tlist_Ctags_Cmd = '/usr/bin/ctags'
let Tlist_Auto_Open = 1 " 讓 Tlist 自動開啟
let Tlist_Show_One_File = 1 " 不同時顯示多個文件的tag,只顯示當前文件的
let Tlist_Exit_OnlyWindow = 1 " 如果taglist窗口是最後一個窗口,則退出vim
let Tlist_Use_Right_Window = 1 " 在右側窗口中顯示taglist窗口
nnoremap <silent> <F8> :TlistToggle<CR> " F8 為開啟/關閉 Tlist 的快速鍵

ctrl + w 再加方向鍵可以切換 window,例如加右鍵就是跳到右邊的 window。

patch

有時候切換 tab 會出現 error Taglist error: Error detected while processing function <SNR>29_Tlist_Refresh_Folds,可用 patch 解決:$ patch -p0 ~/.vim/plugin/taglist.vim taglist.diff

cscope

$ sudo apt-get install cscope

切到 project 的資料夾產生 cscope 資料庫:

$ cscope -RC

之後用 vim 開啟 source code,可用 :cs 指令使用 cscope 的功能。也可以在 ~/.vim/plugin 中放 cscope_map.vim 加快捷鍵:

1
2
3
4
5
6
7
ctrl+\ s "s表Symbol,列出所有參考到游標所在字串的地方,包含定義和呼叫。
ctrl+\ g "Find this definition
ctrl+\ c "c表Call,列出所有以游標所在字串當函數名的地方。
ctrl+\ t "t表Text,列出專案中所有出現游標所在字串的地方。
ctrl+\ f "f表File,以游標所在字串當檔名,開啟之。
ctrl+\ i "i表Include,以游標所在字串當檔名,列出所有include此檔的檔案。
ctrl+\ d "d表calleD,以游標所在字串當函式名,列出所有此函式呼叫的函式。

自動 highlight

.vim/plugin 加入 autohighlight.vim

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
" Highlight all instances of word under cursor, when idle.
" Useful when studying strange source code.
" Type z/ to toggle highlighting on/off.
nnoremap z/ :if AutoHighlightToggle()<Bar>set hls<Bar>endif<CR>
function! AutoHighlightToggle()
let @/ = ''
if exists('#auto_highlight')
au! auto_highlight
augroup! auto_highlight
setl updatetime=4000
echo 'Highlight current word: off'
return 0
else
augroup auto_highlight
au!
au CursorHold * let @/ = '\V\<'.escape(expand('<cword>'), '\').'\>'
augroup end
setl updatetime=500
echo 'Highlight current word: ON'
return 1
endif
endfunction

輸入 z/ 可開關自動 highlight 的功能。

reference here

a.vim

在 header 及 source 之間切換。script

切換指令為 :A

自動補括號

Auto Pairs

將 script 放到 ~/.vim/plugin/ 底下。

Ref

大學時因為很任性的理由沒有修軟體工程,所以 agile 是一直到開始工作才聽到。之前是聽過 XP 跟 pair programming,不過沒有很清楚在幹嘛。話說 pair programming 實在要看狀況用,像我這種基本上屬於內向害羞的類型,用 pair programming 大多數狀況下根本直接腦殘化…= =

之前被拐去上 scrum 的課,說不定是因為講師一直強調 agile 本身精神很重要云云,有個想法一直在腦袋徘徊不去(?),只好來 murmur 一下看能不能清爽些。阿不過這篇只講對 agile 精神的想法,scrum 方法我不會(欸)。

我將 agile 精神類比為適應,因為根本上概念是共通的。適應就是因應改變,無論那改變來自外在環境抑或是自身變化。差別只在於生物上各種生物有不同的適應方式,人對於環境以及種種變化所做出的調整也是適應,而 agile 則是將這樣的想法套用於組織性的軟體開發。

需求改變(外在環境)、內部人員的不同(內在改變)等等都是變化,有了基本精神後當然是以各種工具與方法達成。而現在有許多經過不少人血淚斑斑的試驗過、覺得還不錯的方法可以使用,但是最後依然要找出適合組織與情境的方式。

我是從個人的角度出發去看「因應變化」這件事。因應變化常常帶著不確定性,所以有時候不太討喜,但是近年我喜歡這種會因應變化的觀念,因為這代表彈性、可能性以及選擇。我喜歡用「成長」的角度去看待改變,改變不見得是成長,但是沒有改變幾乎難以有所成長。有改變代表有新東西,有新東西代表有新選項,有新選項代表有不同未來的可能性。倘若不滿意現況,如果希冀有所不同,又怎麼能夠期望同樣的方式能帶來不同的結果?

至於結果是更好還是更壞,如果以從個人角度出發來看,我是不太在意。因為覺得不好,再改就是了,這不正是彈性的好處?我傾向相信,終究會慢慢摸索出適合的方式。而那方式,也可能只適用於某一陣子,那只是一個暫時的答案,不是絕對。

定義物件間一對多的關係,當一個物件改變狀態,其他相依者都會收到通知並自動被更新。

適用場合

某個物件擁有某些 state,還有一堆其他物件很在乎這些 state 有沒有改變,這些物件在 state 改變時需要做些事情。這邊的 state 是抽象概念,舉凡一個 button 被 click、load data 的 progress、load data 完成與否、畫面是否被修改等等都可以看作 state。另外,一個 object 可能會有很多種 state。

網路上很喜歡用的說法:observer 是一種訂報紙的概念。簡言之,有間報社(subject or observable),要訂報紙的人(observer)跟報社說他要訂報紙,之後報社有新報紙了(狀態改變)就會通知這些訂戶,訂戶拿到報紙後或許拿來看、或許拿來墊便當(?),想幹嘛就幹嘛。如果一個訂戶不想收到新報紙了,就跟報社說一聲退出訂閱,之後報社就不會再通知他了。

再舉另一個例子:UI 操作上滑鼠點了某個東西,程式其他部分需要因應點選做些事情時也適用 observer pattern。

UML

Observer Pattern

Observable 中會有些 state,state 變化時 call NotifyXXX() 通知 observer 們 update。一種做法是 NotifyXXX() call observer 的 update(),而 obsever 會在各自的 update() 中 implement observable 狀態改變時要做的事。

observer 如何取得 observable 的資料?

observer 拿 observable 的資料有兩種方式:

  • push:由 observable 在通知時一起傳給 observer。
  • pull:observer 用 observable 提供的 getter 取得資料。

我是還感覺不出來兩種方式的好壞啦……

有的沒的

  • observer 在 update 時不可以依賴被 notify 的順序,因為不同的 observable 實作可能導致結果不同。
  • Java 裡有現成的 observer pattern API。
  • Java 的 ActionListener 機制是種 observer pattern 的實作──當某件事發生時會 call ActionListener 相應的 function。

Qt signal-slot 轉成 native 寫法

Qt 的 signal-slot 在用途是特定的狀況下,例如某些 signal-slot 是特別針對某個操作時,可以改成自己實作 observer 並取代之。什麼時候會這麼做?不想用 Qt 做某些 notify 甚至不想用 Qt 的時候…

我覺得 observer 跟 listener 差不太多,這裡說的 listener 是稍微簡化的 observer,不會有如上述的 observable interface,而是 listenable 的 class 各自自行增加 listener 相關 member 及 function。

如何將 Qt 的 signal-slot 改成 native 的 listener 寫法、自己處理 notify 呢?

  1. 看懂原本 notify 的流程,區分誰是 listener 誰是 listenable。一般來說 signal 的 sender 是 listenable,signal 的 receiver 是 listener。確認要改哪些 signal、slot、connect()、disconnect()。
  2. 在 listenable class 增加 listener list、add 跟 remove listener function。
  3. 在 listener interface 增加 update function,也就是當某件事發生時會被 call 的 function。
  4. 修改 listenable class 的 emit signal,改為 call listener 的 update function 或 NotifyXXX(),由 NotifyXXX() 再 call listener 的 update function。如果有多處需要通知 listener,建議寫成 NotifyXXX()。如果只有一個地方需要通知,也可以不用 NotifyXXX()。
  5. listener class 繼承 listener interface 並 implement update function。將原本的 slot 改為一般 function,接著看要由 update function call 還是直接 rename 為 update function。如果原本是 auto 或 queued connection 要注意 thread issue。
  6. 修改 listener class,將 connect() 及 disconnect() 改為 call add 及 remove observer。
  7. 需注意 listener 及 listenable object 的生命週期,如果 listenable 活得比 listener 久,listener 死之前要將自己從 listenable 的 listener list 中移除。

Qt 的 signal connection 有些特殊功能也要一併處理:

  • queued connection 可能隱含換 thread,需釐清原本程式是否意圖使用換 thread 機制。
    如果有換 thread 就不是單純 observer pattern 可以處理的,可能要改用 thread + event loop,或 listener 的部分可以使用 Qt 則在 listener 以 signal-slot 換 thread。
  • direct connection 幾乎等同直接 call function,不需要額外處理。
  • unique connection 代換作法之一是在 register observer 時做 filter,已經是 observer 的 object 就不再加入 observer list。

Ref

今天看到這篇文章,加上近日對於整理筆記之厭倦,在想到底寫筆記是寫來幹嘛的?

那篇文章表示不用寫「資訊」,但以學習知識來說,我還是需要一點「重點整理」方便日後查找,尤其是一些從書上看來、稍微消化過但也還沒熟到整個記下來的部分。資訊的確幾乎都可以找 google 大神,但是重新 google 到的資訊可能跟我的印象有出入,還有一些眉眉角角不見得記得,有個地方作為資訊的集中地對我還是有用的。

只不過,有時候似乎會趨向小時候在課堂上抄寫筆記的狀況,像在抄書。這感覺就怪怪的,有時候分不清楚自己是在抄書還是那真是重點(好吧,我猜如果覺得很厭倦的話大概就是在抄書或者已經不需要寫了)。想著想著發現原來今年初寫過筆記這回事

以上是跟專業、學習知識有關的「筆記」。至於看其他書啊、聽演講啊之類的,我比較會寫心得感想,像那篇文章所說的第二種筆記。在專業有關的學習上,這種感想會在書裡變眉批,這裡一句、那裡一段的,有時候是疑問,有時候是吐槽(欸?),但這種亂七八糟的心得就不太會去整理了。

可能會慢慢增加對於專業領域的想法吧,我對時事或一些事情會有某些想法,但很奇怪,對於自身專業卻不太有自己的想法。

C++ 不像 Java 有 interface,這邊說的 interface 是所有 function 皆為 pure virtual function 的 abstract class。

interface 依然要有 virtual destructor,否則用 interface pointer 去砍 object 的時候會變成 undefined behavior。如果希望 interface 中的 virtual destructor 仍是 pure virtual,可以這樣寫:

1
2
3
4
5
6
7
class Interface
{
public:
virtual ~Interface() = 0;
};

inline Interface::~Interface() { }

由於只要有 class 繼承 Interface,Interface 的 destructor 就會被 derived class call 到,所以必須要有 definition 才不會 link error。

virtual destructor 另一個使用場合──希望訂一個 interface 但不想訂定其中要有什麼 function──不想訂 function 但又需要該 class 是 abstract class 時就可用 virtual destructor 達到目的。聽起來有點怪,我遇到這種狀況是希望有個 interface 可以代表某種類的 object,但是在 interface 的階段卻又無法確定 derived class 會有那些 function,最後因為覺得結構上不需要而沒有真的使用,不曉得有沒有其他更好的方法?

2018-12:現在看看覺得「希望訂一個 interface 但不想訂定其中要有什麼 function」的情況有點怪……又想不起來之前遇到什麼了……要訂 interface 至少要有基本 function,否則是否先不訂 interface、先做 class,等漸漸看到有共通的 function 時再訂定 interface?

Ref