From f978e420361704bd7531e2b57905a308a3a012c8 Mon Sep 17 00:00:00 2001 From: Artjoms Nemiro Date: Wed, 19 Jul 2017 13:41:46 +0300 Subject: [PATCH] structural/proxy: implement structural proxy pattern (#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 --- .gitignore | 14 ++++- README.md | 2 +- structural/proxy.md | 45 ++++++++++++++ structural/proxy/main.go | 127 +++++++++++++++++++++++++++++++++++++++ 4 files changed, 185 insertions(+), 3 deletions(-) create mode 100644 structural/proxy.md create mode 100644 structural/proxy/main.go diff --git a/.gitignore b/.gitignore index daf913b..f72f0cd 100644 --- a/.gitignore +++ b/.gitignore @@ -19,6 +19,16 @@ _cgo_export.* _testmain.go -*.exe -*.test *.prof +# Test binary, build with `go test -c` +*.test +# Binaries for programs and plugins +*.exe +*.dll +*.dylib + +# JetBrains project files +.idea/ + +# Output of the go coverage tool, specifically when used with LiteIDE +*.out \ No newline at end of file diff --git a/README.md b/README.md index 4d7dbf4..7d9f5f7 100644 --- a/README.md +++ b/README.md @@ -30,7 +30,7 @@ A curated collection of idiomatic design & application patterns for Go language. | [Decorator](/structural/decorator.md) | Adds behavior to an object, statically or dynamically | ✔ | | [Facade](/structural/facade.md) | Uses one type as an API to a number of others | ✘ | | [Flyweight](/structural/flyweight.md) | Reuses existing instances of objects with similar/identical state to minimize resource usage | ✘ | -| [Proxy](/structural/proxy.md) | Provides a surrogate for an object to control it's actions | ✘ | +| [Proxy](/structural/proxy.md) | Provides a surrogate for an object to control it's actions | ✔ | ## Behavioral Patterns diff --git a/structural/proxy.md b/structural/proxy.md new file mode 100644 index 0000000..8a33b9b --- /dev/null +++ b/structural/proxy.md @@ -0,0 +1,45 @@ +# Proxy Pattern + +The [proxy pattern](https://en.wikipedia.org/wiki/Proxy_pattern) provides an object that controls access to another object, intercepting all calls. + +## Implementation + +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. + +Short idea of implementation: +```go + // To use proxy and to object they must implement same methods + type IObject interface { + ObjDo(action string) + } + + // Object represents real objects which proxy will delegate data + type Object struct { + action string + } + + // ObjDo implements IObject interface and handel's all logic + func (obj *Object) ObjDo(action string) { + // Action behavior + fmt.Printf("I can, %s", action) + } + + // ProxyObject represents proxy object with intercepts actions + type ProxyObject struct { + object *Object + } + + // ObjDo are implemented IObject and intercept action before send in real Object + func (p *ProxyObject) ObjDo(action string) { + if p.object == nil { + p.object = new(Object) + } + if action == "Run" { + p.object.ObjDo(action) // Prints: I can, Run + } + } +``` + +## Usage +More complex usage of proxy as example: User creates "Terminal" authorizes and PROXY send execution command to real Terminal object +See [proxy/main.go](proxy/main.go) or [view in the Playground](https://play.golang.org/p/mnjKCMaOVE). diff --git a/structural/proxy/main.go b/structural/proxy/main.go new file mode 100644 index 0000000..e8a50f8 --- /dev/null +++ b/structural/proxy/main.go @@ -0,0 +1,127 @@ +package main + +import ( + "fmt" +) + +// For example: +// we must a execute some command +// so before that we must to create new terminal session +// and provide our user name and command +func main() { + // Create new instance of Proxy terminal + t, err := NewTerminal("gopher") + if err != nil { + // panic: User cant be empty + // Or + // panic: You (badUser) are not allowed to use terminal and execute commands + panic(err.Error()) + } + + // Execute user command + excResp, excErr := t.Execute("say_hi") // Proxy prints to STDOUT -> PROXY: Intercepted execution of user (gopher), asked command (say_hi) + if excErr != nil { + fmt.Printf("ERROR: %s\n", excErr.Error()) // Prints: ERROR: I know only how to execute commands: say_hi, man + } + + // Show execution response + fmt.Println(excResp) // Prints: gopher@go_term$: Hi gopher +} + +/* + From that it's can be different terminals realizations with different methods, propertys, yda yda... +*/ + +// ITerminal is interface, it's a public method whose implemented in Terminal(Proxy) and Gopher Terminal +type ITerminal interface { + Execute(cmd string) (resp string, err error) +} + +// GopherTerminal for example: +// Its a "huge" structure with different public methods +type GopherTerminal struct { + // user is a current authorized user + User string +} + +// Execute just runs known commands for current authorized user +func (gt *GopherTerminal) Execute(cmd string) (resp string, err error) { + // Set "terminal" prefix for output + prefix := fmt.Sprintf("%s@go_term$:", gt.User) + + // Execute some asked commands if we know them + switch cmd { + case "say_hi": + resp = fmt.Sprintf("%s Hi %s", prefix, gt.User) + case "man": + resp = fmt.Sprintf("%s Visit 'https://golang.org/doc/' for Golang documentation", prefix) + default: + err = fmt.Errorf("%s Unknown command", prefix) + } + + return +} + +/* + And now we will create owr proxy to deliver user and commands to specific objects +*/ + +// Terminal is a implementation of Proxy, it's validates and sends data to GopherTerminal +// As example before send commands, user must be authorized +type Terminal struct { + currentUser string + gopherTerminal *GopherTerminal +} + +// NewTerminal creates new instance of terminal +func NewTerminal(user string) (t *Terminal, err error) { + // Check user if given correctly + if user == "" { + err = fmt.Errorf("User cant be empty") + return + } + + // Before we execute user commands, we validate current user, if he have rights to do it + if authErr := authorizeUser(user); authErr != nil { + err = fmt.Errorf("You (%s) are not allowed to use terminal and execute commands", user) + return + } + + // Create new instance of terminal and set valid user + t = &Terminal{currentUser: user} + + return +} + +// Execute intercepts execution of command, implements authorizing user, validates it and +// poxing command to real terminal (gopherTerminal) method +func (t *Terminal) Execute(command string) (resp string, err error) { + // If user allowed to execute send commands then, for example we can decide which terminal can be used, remote or local etc.. + // but for example we just creating new instance of terminal, + // set current user and send user command to execution in terminal + t.gopherTerminal = &GopherTerminal{User: t.currentUser} + + // For example our proxy can log or output intercepted execution... etc + fmt.Printf("PROXY: Intercepted execution of user (%s), asked command (%s)\n", t.currentUser, command) + + // Transfer data to original object and execute command + if resp, err = t.gopherTerminal.Execute(command); err != nil { + err = fmt.Errorf("I know only how to execute commands: say_hi, man") + return + } + + return +} + +// authorize validates user right to execute commands +func authorizeUser(user string) (err error) { + // As we use terminal like proxy, then + // we will intercept user name to validate if it's allowed to execute commands + if user != "gopher" { + // Do some logs, notifications etc... + err = fmt.Errorf("User %s in black list", user) + return + } + + return +}