Skip to content

Commit 3ab3e72

Browse files
committed
add structural proxy pattern
1 parent e51aaae commit 3ab3e72

File tree

4 files changed

+177
-0
lines changed

4 files changed

+177
-0
lines changed

README.md

+1
Original file line numberDiff line numberDiff line change
@@ -15,3 +15,4 @@
1515
* [Binary Tree compositions](structural/binary-tree-compositions) [:notebook:](https://en.wikipedia.org/wiki/Binary_tree)
1616
* [Adapter](structural/adapter) [:notebook:](https://en.wikipedia.org/wiki/Adapter_pattern)
1717
* [Bridge](structural/bridge) [:notebook:](https://en.wikipedia.org/wiki/Bridge_pattern)
18+
* [Proxy](structural/proxy) [:notebook:](https://it.wikipedia.org/wiki/Proxy_pattern)

structural/proxy/README.md

+61
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
# Proxy
2+
3+
The design pattern usually wraps an object to hide some of its characteristics. The objectives of this patterns are ti hide an object behind the proxy so the features can be hidden, restricted and so on. Again, it provide a new abstraction layer that is easy to work with, and can be changed easily.
4+
5+
In this example we have a database of users.
6+
7+
```go
8+
type User struct {
9+
ID int32
10+
}
11+
12+
type UserList []User
13+
14+
type UserFinder interface {
15+
FindUser(id int32) (User, error)
16+
}
17+
```
18+
19+
We dont use a slice of users because declaring a sequence of structs in this way it possibile to implement the interface UserFinder. A slice cant implement an interface.
20+
21+
And here our proxy. A proxy here contains the database and the cache as UserList. The capacity limit the size of cache. All access to the data will be asked to this proxy. The proxy will save in cache new records. When a record will be asked first will be checked the presence in cache, then in database.
22+
23+
```go
24+
type UserListProxy struct {
25+
SomeDatabase UserList
26+
StackCache UserList
27+
StackCapacity int
28+
}
29+
```
30+
31+
As we said before, we used UserList as sequence of User to implement UserFinder interface. This because our database and our stack are both sequence of user.
32+
33+
```go
34+
func (u *UserListProxy) FindUser(ID int32) (User, error) {
35+
user, err := u.StackCache.FindUser(ID)
36+
if err == nil {
37+
fmt.Println("Returning user from cache")
38+
u.DidLastSearchUsedCache = true
39+
return user, nil
40+
}
41+
42+
user, err = u.SomeDatabase.FindUser(ID)
43+
if err == nil {
44+
fmt.Println("Returning user from database")
45+
u.addUserToStack(user)
46+
u.DidLastSearchUsedCache = false
47+
return user, nil
48+
}
49+
50+
return User{}, fmt.Errorf("User not found")
51+
}
52+
53+
func (u *UserList) FindUser(id int32) (User, error) {
54+
for i := 0; i < len(*u); i++ {
55+
if (*u)[i].ID == id {
56+
return (*u)[i], nil
57+
}
58+
}
59+
return User{}, fmt.Errorf("User %s could not be found\n", id)
60+
}
61+
```

structural/proxy/proxy.go

+62
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
package proxy
2+
3+
import (
4+
"fmt"
5+
)
6+
7+
type User struct {
8+
ID int32
9+
}
10+
11+
type UserFinder interface {
12+
FindUser(id int32) (User, error)
13+
}
14+
15+
type UserList []User
16+
17+
type UserListProxy struct {
18+
SomeDatabase UserList
19+
StackCache UserList
20+
StackCapacity int
21+
DidLastSearchUsedCache bool
22+
}
23+
24+
func (u *UserListProxy) FindUser(ID int32) (User, error) {
25+
user, err := u.StackCache.FindUser(ID)
26+
if err == nil {
27+
fmt.Println("Returning user from cache")
28+
u.DidLastSearchUsedCache = true
29+
return user, nil
30+
}
31+
32+
user, err = u.SomeDatabase.FindUser(ID)
33+
if err == nil {
34+
fmt.Println("Returning user from database")
35+
u.addUserToStack(user)
36+
u.DidLastSearchUsedCache = false
37+
return user, nil
38+
}
39+
40+
return User{}, fmt.Errorf("User not found")
41+
}
42+
43+
func (u *UserListProxy) addUserToStack(user User) {
44+
if len(u.StackCache) >= u.StackCapacity {
45+
u.StackCache = append(u.StackCache[1:], user)
46+
} else {
47+
u.StackCache.addUser(user)
48+
}
49+
}
50+
51+
func (u *UserList) addUser(newUser User) {
52+
*u = append(*u, newUser)
53+
}
54+
55+
func (u *UserList) FindUser(id int32) (User, error) {
56+
for i := 0; i < len(*u); i++ {
57+
if (*u)[i].ID == id {
58+
return (*u)[i], nil
59+
}
60+
}
61+
return User{}, fmt.Errorf("User %s could not be found\n", id)
62+
}

structural/proxy/proxy_test.go

+53
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
package proxy
2+
3+
import (
4+
"math/rand"
5+
"testing"
6+
)
7+
8+
func TestUserListProxy(t *testing.T) {
9+
someDatabase := UserList{}
10+
rand.Seed(2342342)
11+
for i := 0; i < 1000000; i++ {
12+
n := rand.Int31()
13+
someDatabase = append(someDatabase, User{ID: n})
14+
}
15+
16+
proxy := UserListProxy{
17+
SomeDatabase: someDatabase,
18+
StackCapacity: 2,
19+
StackCache: UserList{},
20+
}
21+
22+
knownIDs := [3]int32{
23+
someDatabase[3].ID,
24+
someDatabase[4].ID,
25+
someDatabase[5].ID,
26+
}
27+
28+
t.Run("FindUser = Empty cache", func(t *testing.T) {
29+
user, err := proxy.FindUser(knownIDs[0])
30+
if err != nil {
31+
t.Fatal(err)
32+
}
33+
if user.ID != knownIDs[0] {
34+
t.Error("Returned user name doesnt match with expected")
35+
}
36+
if len(proxy.StackCache) != 1 {
37+
t.Error("After one successful search in an empty cache, the size of it must be one")
38+
}
39+
if proxy.DidLastSearchUsedCache {
40+
t.Error("No user can be returned frmo an emtpy cache")
41+
}
42+
})
43+
44+
t.Run("FindUser = One user ask for same user", func(t *testing.T) {
45+
_, err := proxy.FindUser(knownIDs[0])
46+
if err != nil {
47+
t.Fatal(err)
48+
}
49+
if !proxy.DidLastSearchUsedCache {
50+
t.Error("No user can be returned frmo an emtpy cache")
51+
}
52+
})
53+
}

0 commit comments

Comments
 (0)