Padrões de projeto
São padrões já consagrados com técnicas que nos ajudam no dia-a-dia para solucionar alguns problemas em comum ou fazem com que a nossa aplicação seja mais fácil de mudar no futuro. Aqui vamos propor um exemplo onde eu tenho uma aplicação que recebe dados que representem uma pessoa, mas uma pessoa aqui pode ser tanto uma pessoa física quanto uma pessoa jurídica. Então temos que decidir qual é o tipo da pessoa que está sendo informada, executar algumas validações e depois salvar os dados em um banco de dados.
Builder
Vamos entender primeiramente o que é esse padrão e como ele nos ajudar aqui. O padrão Builder é um padrão para criação de objetos onde são informados os passos necessários para a sua criação. Vamos imaginar que recebemos os nossos dados no seguinte padrão:
{
"name": "Pessoa",
"document": "1234567890",
"type": "PF"
}
Aqui temos uma representação mais ou menos do que seria uma entrada de dados e temos que montar o nosso objeto, conseguimos saber que é uma pessoa do tipo pessoa física pelo campo type
então vamos começar criando nosso arquivo personBuilder.go
:
package builder
type PersonBuilder interface {
SetName(name string)
SetDocument(document string)
Build() Person
}
func GetBuilder(tyeName string) PersonBuilder {
if tyeName == "PF" {
return &NaturalPerson{
Type: "PF",
}
}
if tyeName == "PJ" {
return &LegalPerson{
Type: "PJ",
}
}
return nil
}
Aqui definimos a nossa interface PersonBuilder com as funções para adicionar o nome, o documento e a função encarregada por realizar a construção. Temos também a função GetBuilder que recebe o type
e decide qual é o tipo que deve ser criado, caso seja pessoa física retorna um NaturalPerson caso seja pessoa jurídica retorna um LegalPerson já com um atributo Type.
O que significa esses tipos NaturalPerson e LegalPerson e por que eles são considerados de um tipo PersonBuilder? Vamos ver o arquivo naturalPerson.go
para entender:
package builder
type NaturalPerson struct {
Name string
Document string
Type string
}
func (person *NaturalPerson) SetName(name string) {
person.Name = name
}
func (person *NaturalPerson) SetDocument(document string) {
person.Document = document
}
func (person *NaturalPerson) Build() Person {
return Person{
Name: person.Name,
Document: person.Document,
Type: person.Type,
}
}
Uma coisa interessante no GO é a forma como ele implementa interfaces que é diferente de outras linguagens que são mais orientadas a objetos pois não fica explícito que estamos implementando uma interface e sim ocorre o que se chama Duck Typing que nada mais é do que: se o seu pacote, arquivo ou classe implementar os métodos de uma interface ele passa a ser daquele tipo. Isso segue a idéia de que se um animal anda como um pato, nada como um pato e voa como um pato então esse animal é um pato.
É o que vemos na NaturalPerson que implementa as funções que estão na interface PersonBuilder então concluímos que qualquer arquivo que implemente as funções de PersonBuilder é do mesmo tipo dele.
Podemos ver que a função Build retorna um Person e nessa função são atribuídos Name, Document e Type. Vamos ver esse arquivo:
package builder
type Person struct {
Name string
Document string
Type string
}
Person nada mais é do que uma struct, relativo ao que seria uma classe em outras linguagens, onde temos a representação do que é uma pessoa para a nossa aplicação.
Temos as peças soltas e agora vamos ver como juntar isso para fazer o nosso Builder funcionar:
//Build a PersonBuilder, set values and build a NaturalPerson
personBuilder := builder.GetBuilder("PF")
personBuilder.SetName("John Doe")
personBuilder.SetDocument("21368063004")
naturalPerson := personBuilder.Build()
Aqui basicamente estamos passando como se fosse uma receita de como construir esse objeto e no fim chamamos a nossa função Build que vai construir o nosso objeto e devolvê-lo.
Chain of Responsibility
Então agora temos uma forma de construir os nossos objetos, mas vamos pensar que eu queira executar uma série de passos com esses objetos, sejam validações, transformações e etc. como podemos fazer isso?
Com Chain of Responsibility nós conseguimos criar uma cadeia de responsabilidades onde cada passo consegue processar e decide se passa pro próximo ou não. Aqui vamos supor que conseguimos criar o objeto e queremos persistir em um banco de dados mas antes disso vamos validar se os dados estão certos, por exemplo se o documento é um CPF ou CNPJ correto para aquele tipo de pessoa e etc.
Vamos executar esse processo em passos, vamos validar se o nome está preenchido e depois vamos validar se o CPF/CNPJ está no formato correto. Vamos criar a nossa interface Validation :
package chain
import (
"github.com/guilhermegarcia86/go-patterns/builder"
)
//Validation interface has functions for Chain of Responsability Pattern
type Validation interface {
Execute(builder.Person)
SetNext(Validation)
}
Criamos a nossa interface e ela tem duas funções, uma pra executar a nossa lógica, Execute, que recebe um objeto do tipo Person e executa a nossa lógica e outra que vai chamar a próxima validação, SetNext.
Vamos então criar as nossas validações, primeiro com a ValidationType :
package chain
import (
"log"
"github.com/guilhermegarcia86/go-patterns/builder"
)
//ValidationType struct
type ValidationType struct {
Next Validation
}
//Execute a function to implement the user type validation execution
func (validationType *ValidationType) Execute(user builder.Person) {
if user.ValidationTypeDone {
log.Println("Validation type already done " + user.Type)
validationType.Next.Execute(user)
return
}
log.Println("Validation type user starting " + user.Type)
user.ValidationTypeDone = true
log.Println("Execute next validation")
validationType.Next.Execute(user)
}
//SetNext a function that set the next call
func (validationType *ValidationType) SetNext(next Validation) {
validationType.Next = next
}
Aqui é somente uma prova de conceito onde poderíamos refinar e fazer tratativas de erros e outras validações mais efetivas, mas o importante aqui é que o nosso Execute recebe um objeto do tipo Person e verifica se validação já ocorreu com o atributo user.ValidationTypeDone
e se já aconteceu chama o próximo, caso não tenha ocorrido a validação vai ser executada e chamará o próximo na cadeia.
Para poder ter esse controle de estado, se já executou a validação, precisamos atualizar a nossa Person :
type Person struct {
Name string
Document string
Type string
ValidationNameDone bool
ValidationTypeDone bool
}
Pronto temos os campos que controlam esse estado e eles são do tipo bool
(booleanos). Uma coisa muito importante para esse padrão funcionar é que deve ser definido quem será o último elo dessa corrente e no nosso caso como estamos fazendo só duas validações isso ficará na ValidationName :
package chain
import (
"log"
"github.com/guilhermegarcia86/go-patterns/builder"
)
//ValidationName struct
type ValidationName struct {
Next Validation
}
//Execute a function to implement the user name validation execution
func (validationName *ValidationName) Execute(user builder.Person) {
if user.ValidationNameDone {
log.Println("Validation name already done " + user.Name)
validationName.Next.Execute(user)
return
}
log.Println("Validation name user starting " + user.Name)
user.ValidationNameDone = true
log.Println("Validation finished")
}
//SetNext a function that set the next call
func (validationName *ValidationName) SetNext(next Validation) {
validationName.Next = next
}
Agora vamos ver como seria o funcionamento das validações:
//Begin a Validation chain
validationName := &chain.ValidationName{}
validationType := &chain.ValidationType{}
validationType.SetNext(validationName)
validationType.Execute(naturalPerson)
Proxy
Agora pensando que nós já temos o nosso objeto criado e que já validamos e tratamos ele, precisamos seguir o nosso fluxo proposto que seria salvar essa informação em algum lugar, porém essa tarefa pode se tornar muito custosa dependendo de como será implementada.
Por exemplo se formos salvar em um banco de dados temos todo o custo que é se conectar com o banco de dados, abrir uma transação, commitar e depois fechar, pensando em casos assim existe o padrão Proxy.
Mas antes de explorá-lo vamos entender alguns pontos, se abrir a conexão com um banco é tão difícil por que então eu não tento fazer um código mais performático pra abrir a conexão e fazer todo o resto?
A resposta é que nem sempre nós temos acesso ao código que vai ser executado, pensando nessa ideia de acesso ao banco de dados, geralmente temos bibliotecas prontas onde nós só fazemos as chamadas às suas funções sem que o código de como isso é feito seja exposto pra quem chamou. Porém se tivéssemos alguém que vai chamar uma vez o código “pesado” e vai guardar isso para nós e depois só usa a parte mais fácil sem chamar a parte pesada de novo, mas quem está chamando acha que está chamando o código pesado?
A ideia de proxy vem de alguém que seja representante de outra pessoa e é isso o que ele vai fazer aqui, vamos criar uma interface chamada Database :
package proxy
// Database interface to access
type Database interface {
Access(url string, port string, user string, pass string) (string, error)
}
Aqui temos a definição de um acesso à um banco de dados onde são passado os dados de conexão, agora vamos criar o Proxy :
package proxy
import (
"log"
"time"
)
//Proxy struct
type Proxy struct {
application *Application
url string
port string
user string
pass string
}
//OpenConnection a function that simulates a heavy call to create a database connection
func OpenConnection(url string, port string, user string, pass string) *Proxy {
log.Println("A heavy process to create my connection with database")
time.Sleep(2 * time.Second)
return &Proxy{
application: &Application{},
url: url,
port: port,
user: user,
pass: pass,
}
}
//Access a function that receives params and verifies if has connection opened and do access
func (p *Proxy) Access(url string, port string, user string, pass string) (string, error) {
if *p == (Proxy{}) {
p = OpenConnection(url, port, user, pass)
}
msg, err := p.application.Access(url, port, user, pass)
if err != nil {
log.Fatalln("ERROR")
}
return msg, nil
}
Aqui é onde iríamos esconder a parte difícil da operação, temos a implementação de Access a função que irá abrir a conexão com o banco e a struct que irá controlar se a conexão já foi criada.
A fim de explicar esse conceito a função OpenConnection tem um Sleep que vai aguardar 2 segundos e depois vai chamar o que seria o nosso acesso através do trecho:
msg, err := p.application.Access(url, port, user, pass)
Então aqui podemos ver que quem usar o proxy tem a impressão de que está acessando a função real Access sendo que na verdade está passando pelo proxy.
E por fim o nosso Application que é a representação do que seria a biblioteca do terceiro:
package proxy
import (
"fmt"
)
//Application struct
type Application struct {
}
//Access a function that will do the access
func (a *Application) Access(url string, port string, user string, pass string) (string, error) {
return fmt.Sprintf("Success to connect database in url: %s port: %s user: %s", url, port, user), nil
}
E aqui temos a utilização completa do proxy:
const (
url = "urlDatabase"
port = "3306"
user = "user"
pass = "pass"
)
//Open connection, heavy process
conn := proxy.OpenConnection(url, port, user, pass)
//Access database
msgI, err := conn.Access(url, port, user, pass)
if err != nil {
log.Fatalln("ERROR ", err)
}
log.Println(msgI)
//Do not open connection again
msgII, err := conn.Access(url, port, user, pass)
if err != nil {
log.Fatal("ERROR ", err)
}
log.Println(msgII)
A parte interessante aqui é que executando esse código teremos duas chamadas para função Access porém só a primeira vai demorar 2 segundos pois na próxima chamada o proxy já guardou a conexão.
Código completo
Aqui temos a nossa main.go
onde temos a entrada de dados, construímos os nossos objetos, fazemos as nossas validações e depois simulamos a abertura com o banco de dados e acessamos:
package main
import (
"log"
"github.com/guilhermegarcia86/go-patterns/builder"
"github.com/guilhermegarcia86/go-patterns/chain"
"github.com/guilhermegarcia86/go-patterns/proxy"
)
func main() {
//Build a PersonBuilder, set values and build a NaturalPerson
personBuilder := builder.GetBuilder("PF")
personBuilder.SetName("John Doe")
personBuilder.SetDocument("21368063004")
naturalPerson := personBuilder.Build()
//Build a PersonBuilder, set values and build a LegalPerson
personBuilder = builder.GetBuilder("PJ")
personBuilder.SetName("Cool Company")
personBuilder.SetDocument("47902850000149")
legalPerson := personBuilder.Build()
//Begin a Validation chain
validationName := &chain.ValidationName{}
validationType := &chain.ValidationType{}
validationType.SetNext(validationName)
validationType.Execute(naturalPerson)
validationType.Execute(legalPerson)
const (
url = "urlDatabase"
port = "3306"
user = "user"
pass = "pass"
)
//Open connection, heavy process
conn := proxy.OpenConnection(url, port, user, pass)
//Access database
msgI, err := conn.Access(url, port, user, pass)
if err != nil {
log.Fatalln("ERROR ", err)
}
log.Println(msgI)
//Do not open connection again
msgII, err := conn.Access(url, port, user, pass)
if err != nil {
log.Fatal("ERROR ", err)
}
log.Println(msgII)
}
E temos a saída no console assim:
Validation type user starting PF
Execute next validation
Validation name user starting John Doe
Validation finished
Validation type user starting PJ
Execute next validation
Validation name user starting Cool Company
Validation finished
A heavy process to create my connection with database
Success to connect database in url: urlDatabase port: 3306 user: user
Success to connect database in url: urlDatabase port: 3306 user: user
O código completo pode ser encontrado aqui
Top comments (0)