有點久之前和 yen3 提到我想和人共同開發的東西,這裡寫出其中一個,說明架構,也算是留個紀錄吧。
本程式的架構如圖:
上層模組依賴下層模組,下層模組不會使用到上層模組。粗框代表有plugin介面可擴充。
以下就各模組做說明。
本程式的架構如圖:
上層模組依賴下層模組,下層模組不會使用到上層模組。粗框代表有plugin介面可擴充。
以下就各模組做說明。
Graphic User Interface:
很明顯,就是使用者介面。除了使用Qt之外沒什麼好提的。
很明顯,就是使用者介面。除了使用Qt之外沒什麼好提的。
Album:
用來描述音軌資料的模組。
主要構成是 Track 和 Index 這兩個 struct。Index 用來表現時間資訊,因為它經常需要單位轉換,運算,故分離出來。Track 則用來裝載音軌的資訊。
CUESheet 則是使用 regex 來解析 cue 檔案的 struct 。
這個模組尷尬的地方在於字串的編碼,因為上層 GUI 的字串統一為 Unicode,但是下層解出來的資訊則是編碼過的字串;目前的重點就是讓它可以很順暢地和上下層溝通。
用來描述音軌資料的模組。
主要構成是 Track 和 Index 這兩個 struct。Index 用來表現時間資訊,因為它經常需要單位轉換,運算,故分離出來。Track 則用來裝載音軌的資訊。
CUESheet 則是使用 regex 來解析 cue 檔案的 struct 。
這個模組尷尬的地方在於字串的編碼,因為上層 GUI 的字串統一為 Unicode,但是下層解出來的資訊則是編碼過的字串;目前的重點就是讓它可以很順暢地和上下層溝通。
Panel:
控制輸出格式的使用者介面,為了擴充性而做成 plugin。
它提供輸出格式的選項,並根據選項生出合適的編碼器,轉交給上層進行轉檔。
目前內附有 mp3 的輸出控制。
控制輸出格式的使用者介面,為了擴充性而做成 plugin。
它提供輸出格式的選項,並根據選項生出合適的編碼器,轉交給上層進行轉檔。
目前內附有 mp3 的輸出控制。
Codec:
執行音訊解碼編碼的模組,也提供 plugin 擴充,但是較為複雜。
由於 Reader 和 Writer 基本上架構相同,在這裡只說明 Reader,Writer 就比照辦理。
Reader 提供了 AbstractReader 做為抽象介面,客戶若要擴充則必須繼承並實作它。
它要利用 ReaderFactory 這個物件工廠來生成具象物件,客戶必須註冊一個識別字給這個工廠,通常是格式的副檔名,上層模組就是依照這個識別字來產生物件。
若工廠找不到識別字所對應的產品,就會自動使用預設的編解碼器,其識別字為 "*"。
由於 Qt 的 plugin 只能夠產生一個實體,因此 plugin 的本體並不是 Reader 本身,而是一個索然無味的 class ReaderCreator,裡面只有一個成員方法,用來生成 Reader。
總結來說,若要擴展 Reader,增加新格式支援,你必須:
1.繼承 AbstractReader 並覆寫所有 pure virtual function
2.繼承 ReaderCreator 並覆寫 create() 讓它回傳修改過的 Reader:
執行音訊解碼編碼的模組,也提供 plugin 擴充,但是較為複雜。
由於 Reader 和 Writer 基本上架構相同,在這裡只說明 Reader,Writer 就比照辦理。
Reader 提供了 AbstractReader 做為抽象介面,客戶若要擴充則必須繼承並實作它。
它要利用 ReaderFactory 這個物件工廠來生成具象物件,客戶必須註冊一個識別字給這個工廠,通常是格式的副檔名,上層模組就是依照這個識別字來產生物件。
若工廠找不到識別字所對應的產品,就會自動使用預設的編解碼器,其識別字為 "*"。
由於 Qt 的 plugin 只能夠產生一個實體,因此 plugin 的本體並不是 Reader 本身,而是一個索然無味的 class ReaderCreator,裡面只有一個成員方法,用來生成 Reader。
總結來說,若要擴展 Reader,增加新格式支援,你必須:
1.繼承 AbstractReader 並覆寫所有 pure virtual function
2.繼承 ReaderCreator 並覆寫 create() 讓它回傳修改過的 Reader:
MyReader * MyCreator::create() const { return new MyReader; }3.註冊生成器給 ReaderFactory ,己有提供一些函式方便處理;在全域宣告:
const bool reg = registerReader( "mp3", "krp_mp3" );其中 "mp3" 是程式中使用的識別字,"krp_mp3" 則是 Qt plugin 的名稱,如此一來只要使用:
Reader * r = createReader( "mp3" );即可取得擴充的物件。使用者不需要了解你擴充的全貌。
以上的做法都是為了讓 Codec 和 Panel 對其他模組的耦合降到最低,事實上就算沒有 Codec 和 Panel 的 plugin,主程式也可以執行,只是不能編碼,也不能解碼。若要擴充也不需更動主程式的程式碼。
Common:
集中了各種雜項工具。
text.hpp 裡放置了各種字串處理函式。
tr1.hpp 是為了調整各編譯器對 TR1 和 C99 支援的不同。
os.hpp 裡放的是依各平台不同實作不同的函式,Linux 上就用 linux.cpp 實作,Windows 就用 windows.cpp 實作。
error.hpp 宣告了異常的基礎類別。
即可獲得一個新異常類別。
text.hpp 裡放置了各種字串處理函式。
tr1.hpp 是為了調整各編譯器對 TR1 和 C99 支援的不同。
os.hpp 裡放的是依各平台不同實作不同的函式,Linux 上就用 linux.cpp 實作,Windows 就用 windows.cpp 實作。
error.hpp 宣告了異常的基礎類別。
class ErrorBase : std::exception {}; template< typename ErrorType > class Error : public ErrorBase, public ErrorType {};ErrorBase 是為了避免 template 造成的隱式程式碼膨脹,ErrorType 則提供一個可訂製點和識別型態。你可以自定義一個型別 NewError,再使用 Error
Ablum、GUI 編成執行檔(static link),MP3Panel 和預設的編解碼器為 plugin(dynamic load),Common、Codec、Panel 都包在同一個 dll(dynamic link),因為這三個模組會同時被 plugins 和主程式使用。
大致上是這個樣子...以上有講到一些實作細節,但那是不可靠的,我有可能隨時更改;大致架構則很難再改變,從去年最初交上去的版本開始架構就是這樣,只是擴充方式從靜態編譯變為動態載入,但是程式碼本身卻幾乎全換。
目前比較不穩固的就是 Reader 和 Writer 的介面,兩個都是用 Template Pattern 的方式設計,但是我不確定是否夠通用。
另外 header 似乎也該整理一下...有點雜亂。
目前比較不穩固的就是 Reader 和 Writer 的介面,兩個都是用 Template Pattern 的方式設計,但是我不確定是否夠通用。
另外 header 似乎也該整理一下...有點雜亂。