Skip to content

Commit f978e42

Browse files
LinMADtmrts
authored andcommitted
structural/proxy: implement structural proxy pattern (tmrts#27)
* Update gitignore added JetBrains, LiteIDE and other exclude files * Added example of proxy realisation * Update proxy description with simple example * Update showcase with description, small refactore of code * Update proxy doc * Added comments in example proxy also added link to go play sandbox * Small improvement of proxy example * Update link for play golang * Corrected mistakes, splited user validation in proxy * Updated link to play golang and some mistakes
1 parent 67efe3e commit f978e42

File tree

4 files changed

+185
-3
lines changed

4 files changed

+185
-3
lines changed

.gitignore

+12-2
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,16 @@ _cgo_export.*
1919

2020
_testmain.go
2121

22-
*.exe
23-
*.test
2422
*.prof
23+
# Test binary, build with `go test -c`
24+
*.test
25+
# Binaries for programs and plugins
26+
*.exe
27+
*.dll
28+
*.dylib
29+
30+
# JetBrains project files
31+
.idea/
32+
33+
# Output of the go coverage tool, specifically when used with LiteIDE
34+
*.out

README.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ A curated collection of idiomatic design & application patterns for Go language.
3030
| [Decorator](/structural/decorator.md) | Adds behavior to an object, statically or dynamically ||
3131
| [Facade](/structural/facade.md) | Uses one type as an API to a number of others ||
3232
| [Flyweight](/structural/flyweight.md) | Reuses existing instances of objects with similar/identical state to minimize resource usage ||
33-
| [Proxy](/structural/proxy.md) | Provides a surrogate for an object to control it's actions | |
33+
| [Proxy](/structural/proxy.md) | Provides a surrogate for an object to control it's actions | |
3434

3535
## Behavioral Patterns
3636

structural/proxy.md

+45
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
# Proxy Pattern
2+
3+
The [proxy pattern](https://en.wikipedia.org/wiki/Proxy_pattern) provides an object that controls access to another object, intercepting all calls.
4+
5+
## Implementation
6+
7+
The proxy could interface to anything: a network connection, a large object in memory, a file, or some other resource that is expensive or impossible to duplicate.
8+
9+
Short idea of implementation:
10+
```go
11+
// To use proxy and to object they must implement same methods
12+
type IObject interface {
13+
ObjDo(action string)
14+
}
15+
16+
// Object represents real objects which proxy will delegate data
17+
type Object struct {
18+
action string
19+
}
20+
21+
// ObjDo implements IObject interface and handel's all logic
22+
func (obj *Object) ObjDo(action string) {
23+
// Action behavior
24+
fmt.Printf("I can, %s", action)
25+
}
26+
27+
// ProxyObject represents proxy object with intercepts actions
28+
type ProxyObject struct {
29+
object *Object
30+
}
31+
32+
// ObjDo are implemented IObject and intercept action before send in real Object
33+
func (p *ProxyObject) ObjDo(action string) {
34+
if p.object == nil {
35+
p.object = new(Object)
36+
}
37+
if action == "Run" {
38+
p.object.ObjDo(action) // Prints: I can, Run
39+
}
40+
}
41+
```
42+
43+
## Usage
44+
More complex usage of proxy as example: User creates "Terminal" authorizes and PROXY send execution command to real Terminal object
45+
See [proxy/main.go](proxy/main.go) or [view in the Playground](https://play.golang.org/p/mnjKCMaOVE).

structural/proxy/main.go

+127
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
package main
2+
3+
import (
4+
"fmt"
5+
)
6+
7+
// For example:
8+
// we must a execute some command
9+
// so before that we must to create new terminal session
10+
// and provide our user name and command
11+
func main() {
12+
// Create new instance of Proxy terminal
13+
t, err := NewTerminal("gopher")
14+
if err != nil {
15+
// panic: User cant be empty
16+
// Or
17+
// panic: You (badUser) are not allowed to use terminal and execute commands
18+
panic(err.Error())
19+
}
20+
21+
// Execute user command
22+
excResp, excErr := t.Execute("say_hi") // Proxy prints to STDOUT -> PROXY: Intercepted execution of user (gopher), asked command (say_hi)
23+
if excErr != nil {
24+
fmt.Printf("ERROR: %s\n", excErr.Error()) // Prints: ERROR: I know only how to execute commands: say_hi, man
25+
}
26+
27+
// Show execution response
28+
fmt.Println(excResp) // Prints: gopher@go_term$: Hi gopher
29+
}
30+
31+
/*
32+
From that it's can be different terminals realizations with different methods, propertys, yda yda...
33+
*/
34+
35+
// ITerminal is interface, it's a public method whose implemented in Terminal(Proxy) and Gopher Terminal
36+
type ITerminal interface {
37+
Execute(cmd string) (resp string, err error)
38+
}
39+
40+
// GopherTerminal for example:
41+
// Its a "huge" structure with different public methods
42+
type GopherTerminal struct {
43+
// user is a current authorized user
44+
User string
45+
}
46+
47+
// Execute just runs known commands for current authorized user
48+
func (gt *GopherTerminal) Execute(cmd string) (resp string, err error) {
49+
// Set "terminal" prefix for output
50+
prefix := fmt.Sprintf("%s@go_term$:", gt.User)
51+
52+
// Execute some asked commands if we know them
53+
switch cmd {
54+
case "say_hi":
55+
resp = fmt.Sprintf("%s Hi %s", prefix, gt.User)
56+
case "man":
57+
resp = fmt.Sprintf("%s Visit 'https://golang.org/doc/' for Golang documentation", prefix)
58+
default:
59+
err = fmt.Errorf("%s Unknown command", prefix)
60+
}
61+
62+
return
63+
}
64+
65+
/*
66+
And now we will create owr proxy to deliver user and commands to specific objects
67+
*/
68+
69+
// Terminal is a implementation of Proxy, it's validates and sends data to GopherTerminal
70+
// As example before send commands, user must be authorized
71+
type Terminal struct {
72+
currentUser string
73+
gopherTerminal *GopherTerminal
74+
}
75+
76+
// NewTerminal creates new instance of terminal
77+
func NewTerminal(user string) (t *Terminal, err error) {
78+
// Check user if given correctly
79+
if user == "" {
80+
err = fmt.Errorf("User cant be empty")
81+
return
82+
}
83+
84+
// Before we execute user commands, we validate current user, if he have rights to do it
85+
if authErr := authorizeUser(user); authErr != nil {
86+
err = fmt.Errorf("You (%s) are not allowed to use terminal and execute commands", user)
87+
return
88+
}
89+
90+
// Create new instance of terminal and set valid user
91+
t = &Terminal{currentUser: user}
92+
93+
return
94+
}
95+
96+
// Execute intercepts execution of command, implements authorizing user, validates it and
97+
// poxing command to real terminal (gopherTerminal) method
98+
func (t *Terminal) Execute(command string) (resp string, err error) {
99+
// If user allowed to execute send commands then, for example we can decide which terminal can be used, remote or local etc..
100+
// but for example we just creating new instance of terminal,
101+
// set current user and send user command to execution in terminal
102+
t.gopherTerminal = &GopherTerminal{User: t.currentUser}
103+
104+
// For example our proxy can log or output intercepted execution... etc
105+
fmt.Printf("PROXY: Intercepted execution of user (%s), asked command (%s)\n", t.currentUser, command)
106+
107+
// Transfer data to original object and execute command
108+
if resp, err = t.gopherTerminal.Execute(command); err != nil {
109+
err = fmt.Errorf("I know only how to execute commands: say_hi, man")
110+
return
111+
}
112+
113+
return
114+
}
115+
116+
// authorize validates user right to execute commands
117+
func authorizeUser(user string) (err error) {
118+
// As we use terminal like proxy, then
119+
// we will intercept user name to validate if it's allowed to execute commands
120+
if user != "gopher" {
121+
// Do some logs, notifications etc...
122+
err = fmt.Errorf("User %s in black list", user)
123+
return
124+
}
125+
126+
return
127+
}

0 commit comments

Comments
 (0)