こんにちは。KOUKIです。
以前、Proxyパターンについて記事を書きましたが、別パターンでも実装したので紹介します。

<目次>
Proxyパターン
Proxyパターンは、プログラムの要求を代理人(Proxy)が受け取って処理するパターンです。
DB操作を題材に、理解を深めていきましょう!
Proxyを実装する
まずは、Proxyを実装してみましょう。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
package proxy import "errors" type User struct { ID int32 } type UserFinder interface { FindUser(id string) (User, error) } type UserList []User type UserListProxy struct { MockedDatabase *UserList StackCache UserList StackSize int LastSearchUsedCache bool } func (u *UserListProxy) FindUser(id int32) (User, error) { return User{}, errors.New("Not implemented yet") } |
UserListProxyが件のProxyです。
このProxyは、データベース、キャッシュ、スタックサイズ、キャッシュの使用有無判定をパラメータとして持っています。
Proxyを経由して呼び出すメソッドの結果は、Proxy自身のパラメータで管理されます。例えば、データベースを検索したらその結果をProxyがキャッシングする、といった具合にです。
テストコードの実装
実装の途中ですが、テストコードを実装して使い方を確認してみましょう。
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 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 |
package proxy import ( "math/rand" "testing" ) func init() { rand.Seed(2342423) } func TestUserListProxy(t *testing.T) { mockedDatabase := UserList{} // ランダムなUserIDを作成する for i := 0; i < 1000000; i++ { n := rand.Int31() mockedDatabase = append( mockedDatabase, User{ID: n}) } // Proxy設定(mock database, cache, size) proxy := UserListProxy{ MockedDatabase: &mockedDatabase, StackCache: UserList{}, StackSize: 2, } // いくつかIDを取り出す knowIDs := [3]int32{ mockedDatabase[3].ID, mockedDatabase[4].ID, mockedDatabase[5].ID, } // Proxyを経由してUserIDを取得 t.Run("FindUser -Empty cache", func(t *testing.T) { user, err := proxy.FindUser(knowIDs[0]) if err != nil { t.Error(err) } if user.ID != knowIDs[0] { t.Error("Returned user name doesn't match with expected") } if len(proxy.StackCache) != 1 { t.Error("After one successful search in an empty cache, the size of it must be one") } if proxy.LastSearchUsedCache == true { t.Error("No user can be returned from an empty cache") } }) // ↑のテストでProxyのパラメータは書き換えられる t.Run("FindUser - One user, ask for the same user", func(t *testing.T) { proxy.FindUser(knowIDs[0]) if len(proxy.StackCache) != 1 { t.Error("Cache must not grow if we asked for an object that is stored on it") } if !proxy.LastSearchUsedCache { t.Error("The user should have been returned from the cache") } }) t.Run("FindUser - overflowing the satck", func(t *testing.T) { user1, err := proxy.FindUser(knowIDs[0]) if err != nil { t.Error(err) } user2, err := proxy.FindUser(knowIDs[1]) if proxy.LastSearchUsedCache { t.Error("The user wasn't stored on the proxy cache yet") } user3, err := proxy.FindUser(knowIDs[2]) if proxy.LastSearchUsedCache { t.Error("The user wasn't stored on the proxy cache yet") } for i := 0; i < len(proxy.StackCache); i++ { if proxy.StackCache[i].ID == user1.ID { t.Error("User that should be gone was found") } } if len(proxy.StackCache) != 2 { t.Error("After inserting 3 users the cache should not grow more thant to two") } for _, v := range proxy.StackCache { if v != user2 && v != user3 { t.Error("A non expected user was found on the cache") } } }) } |
コードは長くなりましたが、流れは以下のようになっています。
- Mockデータベースを作成
- Proxy設定
- テスト用のID取得
- Proxyの動作確認
FindUserメソッドの中身を実装していないのでイメージしづらいかもしれません。
Mockデータベースから検索を行うと、結果をProxy自身にキャッシングします。キャッシング数は、StackSizeに記録され、キャッシュの使用の有無については、LastSearchUsedCacheプロパティをチェックする仕様になります。
テストを実行してみましょう。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
$ go test -v -run=TestUserListProxy === RUN TestUserListProxy === RUN TestUserListProxy/FindUser_-Empty_cache proxy_test.go:41: Not implemented yet proxy_test.go:44: Returned user name doesn't match with expected proxy_test.go:47: After one successful search in an empty cache, the size of it must be one === RUN TestUserListProxy/FindUser_-_One_user,_ask_for_the_same_user proxy_test.go:59: Cache must not grow if we asked for an object that is stored on it proxy_test.go:62: The user should have been returned from the cache === RUN TestUserListProxy/FindUser_-_overflowing_the_satck proxy_test.go:69: Not implemented yet proxy_test.go:85: After inserting 3 users the cache should not grow more thant to two --- FAIL: TestUserListProxy (0.03s) --- FAIL: TestUserListProxy/FindUser_-Empty_cache (0.00s) --- FAIL: TestUserListProxy/FindUser_-_One_user,_ask_for_the_same_user (0.00s) --- FAIL: TestUserListProxy/FindUser_-_overflowing_the_satck (0.00s) FAIL exit status 1 FAIL proxy 0.667s |
FindUserメソッドの実装1
UserList構造体にFindUserメソッドを実装します。
1 2 3 4 5 6 7 8 |
func (t *UserList) FindUser(id int32) (User, error) { for i := 0; i < len(*t); i++ { if (*t)[i].ID == id { return (*t)[i], nil } } return User{}, fmt.Errorf("User %d could not be found\n", id) } |
UserList構造体(キャッシュ)はUserのリスト型ですが、上記の様にメソッドを定義することが可能です。
これで、自身が保持しているデータから該当するデータを戻り値として返却できるようになりました。
FindUserメソッドの実装2
次は、ProxyのFindUserメソッドを実装します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
func (u *UserListProxy) FindUser(id int32) (User, error) { // キャッシュから取得 user, err := u.StackCache.FindUser(id) if err == nil { fmt.Println("Returning user from cache") u.LastSearchUsedCache = true return user, nil } // DBから取得 user, err = u.MockedDatabase.FindUser(id) if err != nil { return User{}, err } return user, nil } |
ここでの処理は明快です。キャッシュをチェックし、データが存在しない場合のみDBに問い合わせます。
ユーザーをスタックに入れる
ユーザーをスタックに入れる処理を実装します。
1 2 3 4 5 6 7 8 9 10 11 |
func (t *UserList) addUser(newUser User) { *t = append(*t, newUser) } func (u *UserListProxy) addUserToStack(user User) { if len(u.StackCache) >= u.StackSize { u.StackCache = append(u.StackCache[1:], user) } else { u.StackCache.addUser(user) } } |
この関数を先ほど実装したFindUserメソッドから呼び出します。
1 2 3 4 5 6 7 8 9 |
func (u *UserListProxy) FindUser(id int32) (User, error) { ... u.addUserToStack(user) fmt.Println("Returning user from database") u.LastSearchUsedCache = false return user, nil } |
テストコードの実行
テストコードを実行してみましょう。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
$ go test -v -run=TestUserListProxy === RUN TestUserListProxy === RUN TestUserListProxy/FindUser_-Empty_cache Returning user from database === RUN TestUserListProxy/FindUser_-_One_user,_ask_for_the_same_user Returning user from cache === RUN TestUserListProxy/FindUser_-_overflowing_the_satck Returning user from cache Returning user from database Returning user from database --- PASS: TestUserListProxy (0.03s) --- PASS: TestUserListProxy/FindUser_-Empty_cache (0.00s) --- PASS: TestUserListProxy/FindUser_-_One_user,_ask_for_the_same_user (0.00s) --- PASS: TestUserListProxy/FindUser_-_overflowing_the_satck (0.00s) PASS ok proxy 0.708s |
OKですね。
おわりに
デザインパターンの学ぶメリットは、綺麗なソースコードを実装できるようになることです。しかし、何事においてもやりすぎはいけません。
あまりデザインパターンに固執しすぎると自分だけが読めるソースコードを量産することに繋がります。
基本的な考え方として、「ソースコードの意味を最短で理解できる」コードが最も優れていると思います。
まあ、リーダブルコードの受け入りですが^^
それでは、また!
Go記事まとめ
ソースコード
proxy.go
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 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 |
package proxy import ( "fmt" ) type User struct { ID int32 } type UserFinder interface { FindUser(id string) (User, error) } type UserList []User func (t *UserList) FindUser(id int32) (User, error) { for i := 0; i < len(*t); i++ { if (*t)[i].ID == id { return (*t)[i], nil } } return User{}, fmt.Errorf("User %d could not be found\n", id) } func (t *UserList) addUser(newUser User) { *t = append(*t, newUser) } type UserListProxy struct { MockedDatabase *UserList StackCache UserList StackSize int LastSearchUsedCache bool } func (u *UserListProxy) FindUser(id int32) (User, error) { // キャッシュから取得 user, err := u.StackCache.FindUser(id) if err == nil { fmt.Println("Returning user from cache") u.LastSearchUsedCache = true return user, nil } // DBから取得 user, err = u.MockedDatabase.FindUser(id) if err != nil { return User{}, err } u.addUserToStack(user) fmt.Println("Returning user from database") u.LastSearchUsedCache = false return user, nil } func (u *UserListProxy) addUserToStack(user User) { if len(u.StackCache) >= u.StackSize { u.StackCache = append(u.StackCache[1:], user) } else { u.StackCache.addUser(user) } } |
proxy_test.go
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 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 |
package proxy import ( "math/rand" "testing" ) func init() { rand.Seed(2342423) } func TestUserListProxy(t *testing.T) { mockedDatabase := UserList{} // ランダムなUserIDを作成する for i := 0; i < 1000000; i++ { n := rand.Int31() mockedDatabase = append( mockedDatabase, User{ID: n}) } // Proxy設定(mock database, cache, size) proxy := UserListProxy{ MockedDatabase: &mockedDatabase, StackCache: UserList{}, StackSize: 2, } // いくつかIDを取り出す knowIDs := [3]int32{ mockedDatabase[3].ID, mockedDatabase[4].ID, mockedDatabase[5].ID, } // Proxyを経由してUserIDを取得 t.Run("FindUser -Empty cache", func(t *testing.T) { user, err := proxy.FindUser(knowIDs[0]) if err != nil { t.Error(err) } if user.ID != knowIDs[0] { t.Error("Returned user name doesn't match with expected") } if len(proxy.StackCache) != 1 { t.Error("After one successful search in an empty cache, the size of it must be one") } if proxy.LastSearchUsedCache == true { t.Error("No user can be returned from an empty cache") } }) // ↑のテストでProxyのパラメータは書き換えられる t.Run("FindUser - One user, ask for the same user", func(t *testing.T) { proxy.FindUser(knowIDs[0]) if len(proxy.StackCache) != 1 { t.Error("Cache must not grow if we asked for an object that is stored on it") } if !proxy.LastSearchUsedCache { t.Error("The user should have been returned from the cache") } }) t.Run("FindUser - overflowing the satck", func(t *testing.T) { user1, err := proxy.FindUser(knowIDs[0]) if err != nil { t.Error(err) } user2, err := proxy.FindUser(knowIDs[1]) if proxy.LastSearchUsedCache { t.Error("The user wasn't stored on the proxy cache yet") } user3, err := proxy.FindUser(knowIDs[2]) if proxy.LastSearchUsedCache { t.Error("The user wasn't stored on the proxy cache yet") } for i := 0; i < len(proxy.StackCache); i++ { if proxy.StackCache[i].ID == user1.ID { t.Error("User that should be gone was found") } } if len(proxy.StackCache) != 2 { t.Error("After inserting 3 users the cache should not grow more thant to two") } for _, v := range proxy.StackCache { if v != user2 && v != user3 { t.Error("A non expected user was found on the cache") } } }) } |
コメントを残す
コメントを投稿するにはログインしてください。