Design Pattern 提供某個情境下針對某問題的某種解決方法。這些解法通常是前人整理出來、經過驗證的。

  • 「情境」是某種不斷出現的狀況。
  • 「問題」是在某個情境下希望達到的目標,也可能是情境下的限制。
  • 「解決方法」希望是個 general 的設計,用來解決限制、達到目標。

常用來描述 design pattern 的方式是 GoF 書裡使用的格式。

使用 Design Pattern

能用簡單方法解決問題就用簡單方法。pattern 會為系統增加額外複雜度,只有需要的時候才使用。

使用 pattern 要注意是否有意義以及是否對系統其他部分造成影響,不要為了用而用。如果預期系統在未來會有實際的改變,也可以用 pattern 預先加上彈性。不過要注意,必須是實際的改變,而不是假定的改變。自己覺得可能會改變就用一堆 pattern 也不對,至於什麼叫做「未來實際會有的改變」又是另一個故事了……(喂

熟悉大部分 pattern 後,需要時大概會知道需要什麼 pattern,接著可以參考 pattern 的 motivation 確認想法對不對,再來要考量會不會對系統造成不良影響。設計上確認後,實作可以參考 UML 跟範例了解實作上的眉眉角角。

GoF 把 design pattern 分成三大類:creational、structural、behavioral patterns。有時候不知道確切需要哪個 pattern,可以先對問題分類再從類別中找適合的 pattern。

Design Pattern 分類

Creational pattern

處理「產生 object」。主要的目的有二,一是希望封裝 concrete class,二是想封裝「如何產生並結合 concrete class 的 instance」的過程。

  • Abstract Factory
  • Builder
  • Factory Method
  • Prototype
  • Singleton

Structural pattern

可以合成 class 跟 object 到更大的結構中。

Behavior pattern

重點在 class 與 object 間的互動,以及各自的責任。

Anti-pattern

anti-pattern 就是不好的 pattern,告訴大家什麼叫用不好的解決方式解決一個問題,好避免用到這些方法。一個好的 anti-pattern 除了說明不好的方法之外,會建議改用其他 pattern。不然就沒建設性啦

pattern 世界

除了 design pattern 之外,還有像特定領域如 concurrent 系統的 pattern、組織溝通上的 pattern、UI/UX 的 pattern 等等。pattern 是蠻 general 的概念,在蠻多領域都會出現的,共通的概念是「在面對某種重複出現的情境、狀況或問題時的應對方式」。

Ref

大名鼎鼎的 MVC(Model-View-Controller)是一種 compound pattern,要說是個架構也行,這篇主要用 design pattern 的角度去看 MVC。

compound pattern 是一堆 design pattern 被結合起來使用以解決一般性問題。

MVC 將 component 分成 model、view 以及 controller:

  • model 包含商業邏輯、資料、狀態等等,真正的功能跟做事的 code 會放在 model。
  • view 負責呈現各種資料跟畫面。
  • controller 接收來自 view 的 input,解讀後要 model 做事。依據使用的 model 不同,controller 對 input 的解讀會不同。

MVC 中的 design pattern

MVC 中有 Observer patternStrategy pattern 以及 Composite pattern

model 利用 observer pattern 通知 controller 及 view 狀態改變。如果 observer 使用 pull 的方式取得資料,model 會開 getter。如果用 push,model 會在 notify 時將 state 傳給 observer。view 一般只從 model 取得資料、不會改變 model。要 model 做事或改變 model 是 controller 的工作。有 controller 可以讓 model 跟 view 不要綁那麼緊。

view 跟 controller 使用 strategy pattern,controller 是 view 的 strategy。view 負責管理呈現,任何操作與動作都交給 controller 處理。strategy 讓 view 只需要更換 controller 就能有不同動作。

view 通常用 composite pattern 管理內部的顯示 component,例如 window、panel、widget、button 的階層式結構。

另外,如果需要轉換某個 model 的 interface 就輪到 Adapter pattern 上場啦。例如 db table 的 primary key 改了,新程式碼希望用新的 primary key 當參數又不想改舊 model,就可以用 adapter 轉換 interface。

Murmur

最早遇到 MVC 是在網頁 framework,所以一直理解 model 為「資料」。後來發現 model 好像不只是資料,有文件說 model 包含商業邏輯,但我還是說不上來到底是什麼。現在的理解是絕大多數的程式都是 model,只是把 view 拆出去並且引進 controller 作為類似中介的角色。

讓某個物件有個替身,藉以控制外界對物件的接觸。就是個經紀人的概念。(欸)

Proxy pattern 主要目的在於「控制存取」,被代理的物件可以是遠端主機上的物件、建立成本高(例如需要時間下載)的物件、或者需要做存取權限管控的物件。

UML

Proxy Pattern

很簡單的 UML,client 透過 interface Subject 使用物件,所以它不用知道使用的是真正有功能的物件 RealSubject 還是只是代理人 Proxy。Proxy 依據型態不同會用不同方式取得 RealSubject、使用 RealSubject 的功能,再將結果丟回給 client。

除了操作 interface 外,要讓 client 使用 proxy 而非真正的 object,有個方法是用 factory 產生物件、傳回給 client,不然 client 自己 new 了個 XXXProxy,看也知道不是真正的 object 啊 XD。不過,是不是真有必要再加一層 factory 隱藏使用 proxy 的事實倒是依情況而定,簡單的情境下讓 client 自己去生 XXXProxy 也不會怎麼樣,不見得非得用 factory 增加複雜度。

Proxy 與它的變種們

Remote Proxy

代理另一台電腦上的物件或 service 等等的代理人。使用 proxy 的 client 基本上不會知道 proxy 多做了網路連線、傳遞資料等等的事情,例如 Java 的 RMI(Remote Method Invocation)(這東西應該有點年代了,就當作是個簡單的例子就好)。

Virtual Proxy

代理建立成本很高的 object,所謂成本很高可能是需要比較多時間等等,例如 video object 需要從網路抓影片、回應很慢的 API 等等。等待過程中但又不希望卡到 UI 操作,這時候就可以透過 virtual proxy 先回一個 loading 畫面,等到 video 抓好或 API 回來之後,就由真正的 object 做事。

Protection Proxy

顧名思義,保護真正 object 的 proxy,它會根據權限決定外部可不可以存取真正的 object。

Dynamic Proxy

Java 把事情搞得更複雜,弄了個 dynamic proxy 出來,在 package java.lang.reflect

簡單來說,Java 讓 programmer 連 proxy 都不用寫了,只要寫個 InvocationHandler 讓 proxy object call,runtime 才依據要代理的 object 生出 proxy object。除了讓人更懶之外不太懂為什麼要搞這個出來…

其他奇奇怪怪的 Proxy 們

放關鍵字,需要用再查。

Firewall proxy、Caching proxy、Synchronization proxy、Smart Reference proxy、Complexity Hiding proxy、Copy-On-Wrtie proxy。

網路上常說的 proxy server,就有 cache 跟控制網頁存取的功能。

Proxy 與其他 pattern 的不同

Decorator

proxy 跟 decorator 都是對一個物件包裝,有點像,但是兩者的意圖不同。proxy 的目的是控制存取,decorator 則是增加功能。而且 proxy 通常不會一直包一直包、像洋蔥一樣好多層,decorator 會依據想要的功能一直包。

Adapter

proxy 看到一半,我就想,啊這跟 adapter 不是一樣嗎!不都是在使用的 client 跟真正的 objec 之間加一層!!!(施主冷靜)

還是不一樣啦。adapter 會改變 interface,例如 function 參數不一樣,proxy 會 implement 同樣的 interface。

將狀態封裝成獨立的類別,並把動作 delegate 到目前的狀態物件,讓物件行為隨著內在狀態改變而改變。

使用情境

系統中有些狀態以及操作,而操作會依據狀態不同而不同,通常這時候能畫出 state machine。

依據 state machine,我們可能就會寫出這樣的 code:

1
2
3
4
5
6
7
8
9
10
// pseudo code
function operation() {
if (state == STATE_A) {
// do sth.
}
else if (state == STATE_B) {
// do other thing
}
// ...
}

operation() 通常是 state machine 裡的動作。

不只 operation() 裡有很多 if,還可能有很多像 operation() 的 function。如果其中有 state 需求變動,或者要加入新的 state,改這段 code 頭就痛了……

State Pattern 是將 state 封裝成一個個 class(封裝會變動的部份)。將在不同 state 要做的操作,分別放到每個 state class 中,並透過將操作 delegate 給 state object 做,來消除原本 code 裡一堆的 if。

來點例子

用《深入淺出設計模式》的例子,糖果機與狀態 class 們的 code 擷取如下:

1
2
3
4
5
6
public interface State {
public void insertQuater();
public void ejectQuater();
public void turnCrank();
public void dispense();
}
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
public class NoQuarterState implements State {
GumballMachine gumballMachine;

public NoQuarterState(GumballMachine machine) {
this.gumballMachine = machine; // 擁有糖果機的參考
}

public void insertQuater() {
// do sth.
// 改變糖果機的 state
gumballMachine.setState(gumballMachine.getHasQuarterState());
}

public void ejectQuater() {
// do sth.
}

public void turnCrank() {
// do sth.
}

public void dispense() {
// do sth.
}
}

其他的 state class 就不一一列舉啦~依此推類。

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
29
30
31
32
33
34
35
36
37
38
39
40
41
public class GumballMachine {
State soldOutState;
State noQuarterState;
State hasQuarterState;
State soldState;

// 不需要用表示 state 的 const 來紀錄狀態,直接用 state object 就可以了
State state = soldOutState;
int count = 0;

public GumballMachine(int numberGumballs) {
// 先把所有 state object 生出來
soldOutState = new SoldOutState(this);
noQuarterState = new NoQuarterState(this);
hasQuarterState = new HasQuarterState(this);
soldState = new SoldState(this);

// 初始化 member
this.count = numberGumballs;
if (numberGumballs > 0) {
state = noQuarterState;
}
}

public void insertQuarter() {
// 將操作 delegate 給 state object
state.insertQuarter();
}

public void ejectQuarter() {
state.ejectQuarter();
}

// other operations

public State getHasQuarterState() {
return this.hasQuarterState;
}

// other getters for State
}

糖果機將操作 delegate 給 state object 做,state object 會做事並且改變糖果機的狀態。

UML

Context::request() call state.operation1(),將事情 delegate 給 state object 做。在糖果機的例子裡,GumballMachine 就是 Context

由誰處理狀態轉換?

狀態轉換可以在 state object 做,也可以在 context 做,糖果機的例子是由 state object 做狀態轉換。

一般原則是狀態轉換是固定的時候,適合在 context 做,而轉換會在 runtime 因為條件不同而有不同時適合在 state object 做。

在 state object 轉換狀態的缺點是會讓 state object 們互相依賴,解決這問題的方式之一是讓 state object 可以透過 context 取得其他 state object,也就是糖果機的 state getters。

與 Strategy Pattern 比較

State pattern 跟 Strategy pattern 的 UML 根本長得一樣。兩個 pattern 的差異在於「意圖」,也可以說是出發點、想達到的目的。

State pattern 將行為封裝在一堆 state object 中,context 隨著狀態不同將動作 delegate 給其中一個 state object。Strategy pattern 則是有很多 algorithm 可以選,通常由使用 algorithm 的使用者(使用 strategy 的 code)決定用哪一個。Strategy 提供了 algotithm 選擇上的彈性但由使用者主導。State 則以狀態為主,狀態改變會導致行為改變。

好萊塢守則

低階 component 可以掛到高階 component 上,但是由高階 component 決定何時 call 低階 component,低階 component 不該 call 高階 component。

這是為了防止 component 之間的 dependency 爛掉,例如高階 component 使用低階 component 也就 depend on 低階 component,低階 component 如果又使用高階 component 就變成互相依賴,再多來幾個 component 就大家通通糊在一起。

Least Knowledge

系統中的 object 應該只跟它很有關係的 object 互動,不要讓太多 class 綁在一起,免得動一個就動到一大堆。動 A 會動到 B 往往是 bug 的來源……

設計時要注意物件所互動的 class 有哪些以及是如何互動的。

針對 interface 寫 code,不針對 implement 寫 code

想使用某一堆類似的 class 時,應該是讓這些 class 有個 interface 並透過 interface 使用 class,而不是針對特定某個 class 寫 code。如果針對 interface 寫 code,當 class 又增加時可以運用多型在一開始生新的 class 的 object,之後使用該 object 的 code 都可以不用改。

多用合成,少用繼承

「合成」像是把東西組起來,是「有」,has-a。「繼承」則是「是」,is-a。

這我還沒有很懂為什麼……只想到一個爛例子,如果 class A 需要某個功能,例如 parse json,假設已經有一個 json parser 的 class,那麼應該是讓 class A 以合成的方式使用 json parser,而不是去繼承 json parser。這個例子很明顯,用繼承會讓 class A 的語意變得很奇怪,不過不是每種狀況都那麼明顯……

trace 聽說比較好讀的 LLVM libcxx。寫法是 template 所以 implementation 都在 header file 裡,這裡以 list 為例。

LLVM libcxx:http://libcxx.llvm.org/index.html

首先,class list 繼承 __list_imp

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
523 template <class _Tp, class _Alloc>
524 class __list_imp
525 {
526 __list_imp(const __list_imp&);
527 __list_imp& operator=(const __list_imp&);
528 protected:
529 typedef _Tp value_type;
533 typedef typename __alloc_traits::void_pointer __void_pointer;
534 typedef __list_iterator<value_type, __void_pointer> iterator;
536 typedef __list_node_base<value_type, __void_pointer> __node_base;
...
552 __node_base __end_;
...
587 iterator begin() _NOEXCEPT
588 {
589 #if _LIBCPP_DEBUG_LEVEL >= 2
590 return iterator(__end_.__next_, this);
591 #else
592 return iterator(__end_.__next_);
593 #endif
594 }
...
805 template <class _Tp, class _Alloc /*= allocator<_Tp>*/>
806 class _LIBCPP_TYPE_VIS_ONLY list
807 : private __list_imp<_Tp, _Alloc>
808 {

__list_imp::begin() 開始,iterator 在 line 534 被定義為 __list_iterator<value_type, __void_pointer>__list_iterator 是個 class:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
273 template <class _Tp, class _VoidPtr>
274 class _LIBCPP_TYPE_VIS_ONLY __list_iterator
275 {
276 typedef __list_node_pointer_traits<_Tp, _VoidPtr> _NodeTraits;
277 typedef typename _NodeTraits::__link_pointer __link_pointer;
278
279 __link_pointer __ptr_;
280
281 #if _LIBCPP_DEBUG_LEVEL >= 2
282 _LIBCPP_INLINE_VISIBILITY
283 explicit __list_iterator(__link_pointer __p, const void* __c) _NOEXCEPT
284 : __ptr_(__p)
285 {
286 __get_db()->__insert_ic(this, __c);
287 }
288 #else
289 _LIBCPP_INLINE_VISIBILITY
290 explicit __list_iterator(__link_pointer __p) _NOEXCEPT : __ptr_(__p) {}
291 #endif

__list_iterator constructor 存下傳進來的 __link_pointer,也就是 __list_imp::__end_.__next___list_node_pointer_traits 是個 struct,裡面定義了 type __link_pointer,然後這東西追下去有點沒完沒了,照字面意思當它是個 pointer 吧。

__end_ 是個 __node_base,也就是 __list_node_base<value_type, __void_pointer>

1
2
3
4
5
6
7
8
9
10
228 template <class _Tp, class _VoidPtr>
229 struct __list_node_base
230 {
231 typedef __list_node_pointer_traits<_Tp, _VoidPtr> _NodeTraits;
232 typedef typename _NodeTraits::__node_pointer __node_pointer;
233 typedef typename _NodeTraits::__base_pointer __base_pointer;
234 typedef typename _NodeTraits::__link_pointer __link_pointer;
235
236 __link_pointer __prev_;
237 __link_pointer __next_;

那麼 __end_ 又是做什麼用的呢?我猜它跟 list 的最後一個 node 之類的有關係,所以看看 push_back() 是不是會動到它。

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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
1537 template <class _Tp, class _Alloc>
1538 void
1539 list<_Tp, _Alloc>::push_front(const value_type& __x)
1540 {
1541 __node_allocator& __na = base::__node_alloc();
1542 typedef __allocator_destructor<__node_allocator> _Dp;
1543 unique_ptr<__node, _Dp> __hold(__node_alloc_traits::allocate(__na, 1), _Dp(__na, 1));
1544 __node_alloc_traits::construct(__na, _VSTD::addressof(__hold->__value_), __x);
1545 __link_pointer __nl = __hold->__as_link();
1546 __link_nodes_at_front(__nl, __nl);
1547 ++base::__sz();
1548 __hold.release();
1549 }
1550
1551 template <class _Tp, class _Alloc>
1552 void
1553 list<_Tp, _Alloc>::push_back(const value_type& __x)
1554 {
1555 __node_allocator& __na = base::__node_alloc();
1556 typedef __allocator_destructor<__node_allocator> _Dp;
1557 unique_ptr<__node, _Dp> __hold(__node_alloc_traits::allocate(__na, 1), _Dp(__na, 1));
1558 __node_alloc_traits::construct(__na, _VSTD::addressof(__hold->__value_), __x);
1559 __link_nodes_at_back(__hold.get()->__as_link(), __hold.get()->__as_link());
1560 ++base::__sz();
1561 __hold.release();
1562 }
...
1099 // Link in nodes [__f, __l] at the front of the list
1100 template <class _Tp, class _Alloc>
1101 inline
1102 void
1103 list<_Tp, _Alloc>::__link_nodes_at_front(__link_pointer __f, __link_pointer __l)
1104 {
1105 __f->__prev_ = base::__end_as_link();
1106 __l->__next_ = base::__end_.__next_;
1107 __l->__next_->__prev_ = __l;
1108 base::__end_.__next_ = __f;
1109 }
1110
1111 // Link in nodes [__f, __l] at the front of the list
1112 template <class _Tp, class _Alloc>
1113 inline
1114 void
1115 list<_Tp, _Alloc>::__link_nodes_at_back(__link_pointer __f, __link_pointer __l)
1116 {
1117 __l->__next_ = base::__end_as_link();
1118 __f->__prev_ = base::__end_.__prev_;
1119 __f->__prev_->__next_ = __f;
1120 base::__end_.__prev_ = __l;
1121 }

list 繼承 __list_imp,所以 __link_nodes_at_front()base::__end___list_imp__end_。從上面四個 function 看起來,__end_.__prev_ 指到 list 的最後一個 node,__end_.__next_ 指向 list 第一個 node。也就是說,__end_ 是用來記錄 list 的第一個及最後一個 node。

到這裡回頭看 begin(),它生了個 __list_iterator 並把 list 第一個 node 的 address 丟給 __list_iterator__list_iterator 將 address 記在 __ptr_

最後,iterator 如何取值跟移動。

1
2
3
4
5
350     _LIBCPP_INLINE_VISIBILITY
351 pointer operator->() const
352 {
357 return pointer_traits<pointer>::pointer_to(__ptr_->__as_node()->__value_);
358 }

就是直接拿 value。XD

1
2
3
4
5
6
360     _LIBCPP_INLINE_VISIBILITY
361 __list_iterator& operator++()
362 {
367 __ptr_ = __ptr_->__next_;
368 return *this;
369 }

透過 __next_ 移到下一個 node。

略過很多太細節的 struct,只是想知道 STL 的 iterator 大致是如何取值以及如何做「下一個」的動作的。總結,__list_iterator 是透過 pointer 去 access __list_imp 的內部資料。

其他資料

讓使用者將物件合成為樹狀結構,呈現「部分/整體」的階層關係。如此可讓使用的程式以相同的方式處理單獨或合成的物件。這句話沒解釋的話根本看不懂吧……

讓樹狀結構的 node 都是同樣的 interface,因而可以用相同的方式(相同的 interface)操作 subtree root node(合成物件)以及操作 leaf node(單獨物件)。

UML

Composite Pattern

Composite 就是 tree 中非 leaf 的 node。Client 可以透過 Component interface 同時操作 leaf node 及 subtree node。

這做法會讓 Leaf 繼承一些它不需要的 function 如 getChild()。實作上可以在 Component class 將 function 們的 default 行為 implement 成「不 support 此操作」,由 CompositeLeaf override 對其有意義的 function。至於「不 support 此操作」的實作方式可以是丟出 exception、印 log、return false 等等……

設計取捨

Component interface 包含對 leaf node 沒意義、對 subtree root node 才有意義的操作,就這個 interface 來看包含了兩種責任,操作 leaf node 與操作 subtree root node,不符合 class 單一責任的設計原則。不過呢,這就是設計上的取捨,以單一責任交換能統一操作 tree 中的 node,實際使用上就是要看情境決定哪一個比較重要。

似曾相識的 fu (?)

範例程式生一堆 Component 又 add 來 add 去,看著看著總覺得跟 Qt 的 QStandardItem 寫法很像。看了下 Qt 的 code,QStandardItem 不像是用 composite pattern,應該是打從一開始就定義 item 是個可以有 child 的東西了。

這也是為什麼上一篇是用 QStandardItem 當例子…

每次看 Qt source code 都不知道 d->q-> 到底在幹嘛,翻到 D-Pointer 這篇解釋就來看一下。但是我原本不是要追究這個的ㄚㄚㄚ……

d-pointer pattern

d-pointer 是個 design pattern,也叫 Opaque pointer 或 Pimpl idiom。它分開給外面看的公開 class interface 跟實作的 class。做法是在公開 class 放個實作 class 的 pointer,公開 class 各 function 只是 call implementation class 的 function。公開 class 中跟實作相關的 member 只有那個 pointer,所以 size 會固定是 pointer 的 size。老覺得在哪看過類似的東西……

Qt 用 d-pointer 主要是為了保持 binary compatibility。

Binary Compatibility

有 binary compatibility 表示 application 在以舊版 library compile 後,新版 library binary 可以直接取代舊版 library 而不需要重新 compile application。要達到 binary compatibility 就不能改變已經公開出去的 C++ class 的 size 或 memory layout,即不能增加 member 或者更改 function 跟 member 的順序。如果改了,application 認定的 library class size 或 memory layout 跟新版 library 不同,會把 memory 踩爛掉。

Qt 使用 d-pointer 達到不修改既有公開 class 還能增加 feature 或修改實作細節。

Qt 的實作

基於保持 binary compatibility,Qt 幾乎所有 class 都用 d-pointer 指向真正實作的 class。實作 class 叫 QXXXPrivate,例如 QWidget 就有 QWidgetPrivate,通常定義在 xxx_p.h,例如 qwidget_p.hQXXX 以及 QXXXPrivate 的實作會寫在 qxxx.cpp

為了減少 memory allocation 的次數,QXXXPrivate 的 class 也會有繼承關係。其繼承關係會依循一般 class 的繼承關係,例如 QLabel 繼承 QFrameQLabelPrivate 就會繼承 QFramePrivateQXXXPrivate 大多繼承自 QObjectPrivate

以下 code 擷取自 Qt 5.7.0。

d-pointer & q-pointer

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Q_CORE_EXPORT QObject
{
...
protected:
QScopedPointer<QObjectData> d_ptr;
...
};

class Q_CORE_EXPORT QObjectData
{
public:
QObject *q_ptr;
...
};

class Q_CORE_EXPORT QObjectPrivate : public QObjectData { ... };

好啦~上面是 d-pointer,QObjectPrivate 繼承 QObjectData,而 QObjectd_ptr 指向 QObjectData

有時候實作 class 需要使用公開 class,因此實作 class 會存公開 class 的 pointer,稱為 q-pointer。如上,QObjectDataQObject 的 pointer q_ptr

在 Qt 裡 d-pointer 跟 q-pointer 基本上是繼承自 base class,不過也有例外。

Q_D() & Q_Q () macro

qtbase/src/corelib/global/qglobal.h 定義了這些 function 跟 macro:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
template <typename T> static inline T *qGetPtrHelper(T *ptr) { return ptr; }

#define Q_DECLARE_PRIVATE(Class) \
inline Class##Private* d_func() { return reinterpret_cast<Class##Private *>(qGetPtrHelper(d_ptr)); } \
inline const Class##Private* d_func() const { return reinterpret_cast<const Class##Private *>(qGetPtrHelper(d_ptr)); } \
friend class Class##Private;

#define Q_DECLARE_PUBLIC(Class) \
inline Class* q_func() { return static_cast<Class *>(q_ptr); } \
inline const Class* q_func() const { return static_cast<const Class *>(q_ptr); } \
friend class Class;

#define Q_D(Class) Class##Private * const d = d_func()
#define Q_Q(Class) Class * const q = q_func()

在 class 內會用 Q_DECLARE_PRIVATE(),以 QStandardItem 為例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Q_GUI_EXPORT QStandardItem
{
...
protected:
QScopedPointer<QStandardItemPrivate> d_ptr;

private:
Q_DECLARE_PRIVATE(QStandardItem)
...
};

QStandardItem::QStandardItem() : d_ptr(new QStandardItemPrivate)
{
Q_D(QStandardItem);
d->q_ptr = this;
}

void QStandardItem::setChild(int row, int column, QStandardItem *item)
{
Q_D(QStandardItem);
d->setChild(row, column, item, true);
}

QStandardItem 裡有兩個 inline 的 private function 分別為 QStandardItemPrivate* d_func() 以及 const QStandardItemPrivate* d_fun() const,並且 QStandardItemPrivateQStandardItem 的 friend class,可以 access QStandardItem 的 private member。constructor 生了 QStandardItemPrivate 並將 d_ptr 指過去。

依據 macro 的定義,dQStandardItemPrivate*d_func()qGetPtrHelper(d_ptr) 轉型成 QStandardItemPrivate* 丟出來。d_ptr 是個 QScopedPointer<QStandardItemPrivate>qGetPtrHelper() 將 smart pointer 轉成單純的 pointer(參考這篇)。還沒有看得很懂怎麼轉的,不過先這樣理解吧… 總之 d 是指向 construct 時塞進來的 QStandardItemPrivate 的 pointer。所以最後 QStandardItem::setChild() 是用 QStandardItemPrivate::setChild() 實際做事。

反過來的 Q_Q() 比較簡單。這 macro 的名字真好笑(離題

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class QStandardItemPrivate
{
Q_DECLARE_PUBLIC(QStandardItem)
public:
...
QStandardItem *q_ptr;
};

void QStandardItemPrivate::setChild(int row, int column, QStandardItem *item,
bool emitChanged)
{
Q_Q(QStandardItem);
...
if (rows <= row)
q->setRowCount(row + 1);
...
}

透過 q_func() 拿到的 q 是轉型成 QStandardItem* 的 pointer。

這個例子裡,因為 QStandardItemQStandardItemPrivate 沒有繼承別人,它們自己分別記錄了 d_ptrq_ptr,所以 d_func()q_func() 有沒有轉型根本沒差。但其他繼承 QObjectPrivate 的 class,q_ptr 是從 QObjectData 來的,type 是 QObject*,如果要直接使用 sub class 的 member 就得轉型,d_ptr 同理。除非用 virtual function 做多型,不過這個狀況用 virtual function 很怪,因為這樣 base class QObject 得有所有 sub class 會用到的 interface,這邊顯然不是這個意思,用 virtual function 不合理。

這樣看過之後,對 Qt source code 的結構比較理解了,才不會每次看到 Q_D()Q_Q() 啊都想 Q_Q 了。

《科技渴望社會》第四篇。這篇在說「技術物」如何對人及其生活方式產生影響,「技術物」指的是各種各樣具體的技術或系統,例如核能電廠、鐵路、各種通訊方式、公路網、飛機等等,將「技術」這個較為廣義的詞定義得比較狹義但具體。整篇常常提到政治性、權威、權力、社會組織,我理解為在試圖說明技術物對人的生活以及社會產生的影響。

最偉大的權力不在於君臨天下的帝國威望,而在於權力內化成人們的「生活方式」而無法察覺,更因是生活方式而成為價值觀而為其辯護與效命,從而促成了權力的再生產。而權力的社會運作在於使人們無法思考「另類生活方式」的可能性。

技術作為一種對內的控制性力量,就在於讓我們覺得「只有」一種生活方式。

導讀以汽車作為一種生活方式、一種「必須」舉例。因為認為汽車是必須,所以道路規劃等等就以汽車為主,人們出門以考量汽車可及性與停車便利為主。汽車作為生活方式的一部分,於是很難想像以其他交通方式生活,例如芬蘭的單車與大眾運輸。即使有所想像,甚至會受限於當前硬體設施而較難實現其他交通方式。

引入一個技術的原因不見得是表面上的原因,如引入新技術帶動進步,而可能是達到其他政治或操弄的目的……例如本篇文章提到的低架橋設計,高度故意不足以讓公車通過,使得較依靠公車作為交通手段的族群變得較難前往某些區域,達到了某個程度的隔離目的。

以低架橋這個例子來說,我會認為這是人「刻意」將技術用於較壞的方向。我的想法偏向技術本身是中立的,可以用在好的方向,也可以用到壞的方向,技術本身並不帶有其原生的影響,是好是壞端看人如何使用。

文章後面提到,有時候技術的影響並不見得是當事人的本意與意圖。當事人可能並無將技術用於好或壞的動機,但技術本身仍然帶來某些影響,或好或壞。例如早期公共設施較沒有無障礙設施,這不見得是誰特意為之,但結果讓身心障礙者較難以前往某些地方,而在某個程度上遭到排除。這觀點稍微改變了我認為技術是中立的想法。

技術物甚至會影響圍繞其的社會組織結構與運作方式,例如有了自動化生產系統,原本員工做的事是生產、製造某個東西,但有自動化生產系統後,員工變成是去維護這個系統的運作,而可能為了配合機器而在工作上得要更制式、更機械化。講難聽點,人從做重複性的生產作業,變成機器系統的活零件。

要維持一個龐大的系統順暢運作,組織上就變得需要有階層與管理,需要管理階層去確定每個部分都有人確實的做好維護與運作。但接著要問的是,維持一個系統,是否必然要使用中央集權式的方式予以管理才能有效率並安全的運作?又如,在這個資訊科技講究創新的時代,由上而下、中央集權式的階層管理是否還適用?看到這我有點訝異,雖然有淺淺的想過一項技術會帶來些影響,可是沒有想那麼深。

而當「進步」成為主流,對於技術大多只考慮實用性時,拒絕技術之引進容易被扣上「反進步」的帽子,或許使技術的影響性反而變得不被考量。

murmur:這其實是拉哩拉紮的整理跟心得,目前來說無法更有條理了。看這書大概有四五成是每個字都看得懂但整個句子看起來其實不懂那些字詞真正想表達的意義……

讓使用者能取得一個 collection 內的每個 element,而不需要暴露此 collection 的 implememtation。

Iterator Pattern 封裝了「拿出下一個 element」這件事,讓外部可以不需要知道 collection 實際上以什麼方式實作,如 array、list、stack 等等,就能以一致的方式取得 element。iterator 也讓 collection 的責任更簡單,collection 負責管理一群 object,而 iterator 負責 traverse element。

使用情境

有多種 collection 一同使用,希望能用同樣方式 traverse element 時。

UML

Iterator Pattern

library 及語言支援

C++ STL、Qt、Java 等等的 container 都有支援 iterator。用 iterator 要注意 iterate 過程中能不能更動該 container,因為 iterator 會直接或間接的 access container 內部資料,所以不同實作方式會影響 iterator 的使用。例如 Qt 對 container 用 implicit sharing,所以使用 iterator 期間 copy container 要注意會不會搞爛 iterator 或 iterator 指向的 container 跟預想不同。

在某些語言,如 PHP,會提供 foreach 的語法,這進一步將 iterator 變成 syntax 了。