type Music struct { Id string Name string Artist string Source string Type string }然後開始實現這個音樂庫管理型別,其中我們使用了一個陣列切片作為基礎儲存結構,其他的操作其實都只是對這個陣列切片的包裝,程式碼如下所示。
//manager.go package library import "errors" type MusicManager struct { musics []MusicEntry } func NewMusicManager() *MusicManager { return &MusicManager{make([]MusicEntry, 0)} } func (m *MusicManager) Len() int { return len(m.musics) } func (m *MusicManager) Get(index int) (music *MusicEntry, err error) { if index < 0 || index >= len(m.musics) { return nil, errors.New("Index out of range.") } return &m.musics[index], nil } func (m *MusicManager) Find(name string) *MusicEntry { if len(m.musics) == 0 { return nil } for _, m := range m.musics { if m.Name == name { return &m } } return nil } func (m *MusicManager) Add(music *MusicEntry) { m.musics = append(m.musics, *music) } func (m *MusicManager) Remove(index int) *MusicEntry { if index < 0 || index >= len(m.musics) { return nil } removedMusic := &m.musics[index] // 從陣列切片中刪除元素 if index < len(m.musics)-1 { // 中間元素 m.musics = append(m.musics[:index-1], m.musics[index+1:]...) } elseif index == 0 { // 刪除僅有的一個元素 m.musics = make([]MusicEntry, 0) } else { // 刪除的是最後一個元素 m.musics = m.musics[:index-1] } return removedMusic }實現了這麼重要的一個基礎資料管理模組後,我們應該馬上編寫單元測試,而不是給自己藉口說等將來有空的時候再補上。下面的程式碼實現了 MusicManager 型別的單元測試。
//manager_test.go package library import ( "testing" ) func TestOps(t *testing.T) { mm := NewMusicManager() if mm == nil { t.Error("NewMusicManager failed.") } if mm.Len() != 0 { t.Error("NewMusicManager failed, not empty.") } m0 := &MusicEntry{ "1", "My Heart Will Go On", "Celion Dion", Pop, "http://qbox.me/24501234", MP3} mm.Add(m0) if mm.Len() != 1 { t.Error("MusicManager.Add() failed.") } m := mm.Find(m0.Name) if m == nil { t.Error("MusicManager.Find() failed.") } if m.Id != m0.Id || m.Artist != m0.Artist || m.Name != m0.Name || m.Genre != m0.Genre || m.Source != m0.Source || m.Type != m0.Type { t.Error("MusicManager.Find() failed. Found item mismatch.") } m, err := mm.Get(0) if m == nil { t.Error("MusicManager.Get() failed.", err) } m = mm.Remove(0) if m == nil || mm.Len() != 0 { t.Error("MusicManager.Remove() failed.", err) } }這個單元測試看起來似乎有些偷懶,但它基本上已經覆蓋了 MusicManager 的所有功能,實際上也確實測出了 MusicManager 實現過程中的幾個問題。因此,養成良好的單元測試習慣還是非常有價值的。
func Play(source, mtype string)
這裡沒有直接將 MusicEntry 作為引數傳入,這是因為 MusicEntry 包含了一些多餘的資訊。本著最小原則,我們只需要將真正需要的資訊傳入即可,即音樂檔案的位置以及音樂的型別。
type Player interface {
Play(source string)
}
//play.go package mp import "fmt" type Player interface { Play(source string) } func Play(source, mtype string) { var p Player switch mtype { case "MP3": p = &MP3Player{} case "WAV": p = &WAVPlayer{} default: fmt.Println("Unsupported music type", mtype) return } p.Play(source) }因為我們這個例子並不會真正實現多媒體檔案的解碼和播放過程,所以對於 MP3Player 和 WAVPlayer,我們只實現其中一個作為範例,程式碼如下所示。
//mp3.go package mp import ( "fmt" "time" ) type MP3Player struct { stat int progress int } func (p *MP3Player)Play(source string) { fmt.Println("Playing MP3 music", source) p.progress = 0 for p.progress < 100 { time.Sleep(100 * time.Millisecond) // 假裝正在播放 fmt.Print(".") p.progress += 10 } fmt.Println("nFinished playing", source) }當然,我們也應該對播放流程進行單元測試。因為單元測試比較簡單,這裡就不再列出完整的單元測試程式碼了。
//mplayer.go package main import ( "bufio" "fmt" "os" "strconv" "strings" "pkg/mplayer/mlib" "pkg/mplayer/mp" ) var lib *library.MusicManager var id int = 1 var ctrl, signal chan int func handleLibCommands(tokens []string) { switch tokens[1] { case "list": for i := 0; i < lib.Len(); i++ { e, _ := lib.Get(i) fmt.Println(i+1, ":", e.Name, e.Artist, e.Source, e.Type) } case "add": { if len(tokens) == 6 { id++ lib.Add(&library.MusicEntry{strconv.Itoa(id), tokens[2], tokens[3], tokens[4], tokens[5]}) } else { fmt.Println("USAGE: lib add <name><artist><source><type>") } } case "remove": if len(tokens) == 3 { lib.RemoveByName(tokens[2]) } else { fmt.Println("USAGE: lib remove <name>") } default: fmt.Println("Unrecognized lib command:", tokens[1]) } } func handlePlayCommand(tokens []string) { if len(tokens) != 2 { fmt.Println("USAGE: play <name>") return } e := lib.Find(tokens[1]) if e == nil { fmt.Println("The music", tokens[1], "does not exist.") return } mp.Play(e.Source, e.Type, ctrl, signal) } func main() { fmt.Println(` Enter following commands to control the player: lib list -- View the existing music lib lib add <name><artist><source><type> -- Add a music to the music lib lib remove <name> -- Remove the specified music from the lib play <name> -- Play the specified music `) lib = library.NewMusicManager() r := bufio.NewReader(os.Stdin) for { fmt.Print("Enter command-> ") rawLine, _, _ := r.ReadLine() line := string(rawLine) if line == "q" || line == "e" { break } tokens := strings.Split(line, " ") if tokens[0] == "lib" { handleLibCommands(tokens) } elseif tokens[0] == "play" { handlePlayCommand(tokens) } else { fmt.Println("Unrecognized command:", tokens[0]) } } }
$ go run mplayer.go
Enter following commands to control the player:
lib list -- View the existing music lib
lib add <name><artist><source><type> -- Add a music to the music lib
lib remove <name> -- Remove the specified music from the lib
play <name> -- Play the specified music
Enter command-> lib add HugeStone MJ ~/MusicLib/hs.mp3 MP3
Enter command-> play HugeStone
Playing MP3 music ~/MusicLib/hs.mp3
..........
Finished playing ~/MusicLib/hs.mp3
Enter command-> lib list
1 : HugeStone MJ ~/MusicLib/hs.mp3 MP3
Enter command-> lib view
Enter command-> q