在这个例子中,我们将研究两种策略模式的实现,但它们都以稍微不同的方式实现了相同的结果。它们只是为不同的银行创建了代币。
这两种策略模式在现实世界的场景中都是有效的,所以 "重复的解决方案 "并不一定意味着它是一个糟糕的设计。这只是你如何处理这种商业案例。此外,两种模式都有其优势和劣势。
-
所有的策略都以不同的方式处理相同的工作重复的解决方案,灵活的配置结构。由个别配置参数和具体策略管理。所有策略都可以有自己的配置结构。
-
所有策略以相同的方式处理同一工作简单的解决方案,严格的配置结构。由配置参数管理,需要单一的具体策略。策略依赖于一种类型的配置结构。
这两个例子都依赖于请求对象中的ProviderName 参数。基于给定的值。
-
第一个模型将请求转移到使用其自身配置的相关提供商实现/包(Barclays或Natwest)。
-
第二个模型选择相关的配置并在共同的实现中使用它(access_token或refresh_token)。
所有策略都以不同的方式处理相同的工作
结构
├── main.go
└── obie
├── access_token.go
├── barclays
│ ├── config.go
│ └── provider.go
├── natwest
│ ├── config.go
│ └── provider.go
└── provider.go
文件
main.go
package main
import (
"context"
"net/http"
"github.com/you/obie"
"github.com/you/obie/barclays"
"github.com/you/obie/natwest"
)
func main() {
barcPrv := barclays.NewProvider(barclays.Config{
TokenEndpoint: "https://barclays/token/endpoint",
ClientID: "barclays-client",
ClientSecret: "barclays-secret",
}, http.DefaultTransport)
natwPrv := natwest.NewProvider(natwest.Config{
TokenEndpoint: "https://natwest/token/endpoint",
ClientID: "natwest-client",
ClientSecret: "natwest-secret",
Scope: "natwest scope",
}, http.DefaultTransport)
provider := obie.NewProviderStrategy()
provider.Add(barclays.Name, barcPrv)
provider.Add(natwest.Name, natwPrv)
ctx := context.Background()
_, _ = accessToken(ctx, provider, obie.AccessTokenRequest{ProviderName: barclays.Name})
_, _ = accessToken(ctx, provider, obie.AccessTokenRequest{ProviderName: natwest.Name})
}
func accessToken(ctx context.Context, prv obie.Provider, req obie.AccessTokenRequest) (obie.AccessToken, error) {
return prv.AccessToken(ctx, req)
}
欧比/access_token.go
package obie
type AccessTokenRequest struct {
ProviderName Name
}
type AccessToken struct {
AccessToken string
RefreshToken string
TokenType string
Scope string
ExpiresIn int
}
欧比/provider.go
package obie
import (
"context"
"fmt"
)
type Name string
type Provider interface {
AccessToken(context.Context, AccessTokenRequest) (AccessToken, error)
}
type ProviderStrategy struct {
providers map[Name]Provider
}
func NewProviderStrategy() *ProviderStrategy {
return &ProviderStrategy{
providers: make(map[Name]Provider),
}
}
func (p *ProviderStrategy) Add(name Name, prv Provider) {
p.providers[name] = prv
}
func (p *ProviderStrategy) AccessToken(ctx context.Context, req AccessTokenRequest) (AccessToken, error) {
if _, ok := p.providers[req.ProviderName]; !ok {
return AccessToken{}, fmt.Errorf("access token: unknown provider: %s", req.ProviderName)
}
return p.providers[req.ProviderName].AccessToken(ctx, req)
}
obie/barclays/config.go
package barclays
type Config struct{
TokenEndpoint string
ClientID string
ClientSecret string
}
欧比/barclays/provider.go
package barclays
import (
"bytes"
"context"
"fmt"
"net/http"
"net/http/httputil"
"net/url"
"github.com/you/obie"
)
var Name obie.Name = "Barclays"
type Provider struct {
config Config
transport http.RoundTripper
}
func NewProvider(conf Config, trans http.RoundTripper) Provider {
return Provider{
config: conf,
transport: trans,
}
}
func (p Provider) AccessToken(ctx context.Context, req obie.AccessTokenRequest) (obie.AccessToken, error) {
data := url.Values{}
data.Set("grant_type", "client_credentials")
data.Set("client_id", p.config.ClientID)
data.Set("client_secret", p.config.ClientSecret)
httpReq, _ := http.NewRequestWithContext(
ctx,
http.MethodPost,
p.config.TokenEndpoint,
bytes.NewReader([]byte(data.Encode())),
)
body, _ := httputil.DumpRequest(httpReq, true)
fmt.Println(string(body))
// Send the request and handle the response
// b.transport.RoundTrip(httpReq)
return obie.AccessToken{}, nil
}
欧比/Natwest/config.go
package natwest
type Config struct{
TokenEndpoint string
ClientID string
ClientSecret string
Scope string
}
欧比/Natwest/config.go
package natwest
import (
"bytes"
"context"
"fmt"
"net/http"
"net/http/httputil"
"net/url"
"github.com/you/obie"
)
var Name obie.Name = "Natwest"
type Provider struct {
config Config
transport http.RoundTripper
}
func NewProvider(conf Config, trans http.RoundTripper) Provider {
return Provider{
config: conf,
transport: trans,
}
}
func (p Provider) AccessToken(ctx context.Context, req obie.AccessTokenRequest) (obie.AccessToken, error) {
data := url.Values{}
data.Set("grant_type", "client_credentials")
data.Set("client_id", p.config.ClientID)
data.Set("client_secret", p.config.ClientSecret)
data.Set("scope", p.config.Scope)
httpReq, _ := http.NewRequestWithContext(
ctx,
http.MethodPost,
p.config.TokenEndpoint,
bytes.NewReader([]byte(data.Encode())),
)
body, _ := httputil.DumpRequest(httpReq, true)
fmt.Println(string(body))
// Send the request and handle the response
// b.transport.RoundTrip(httpReq)
return obie.AccessToken{}, nil
}
所有的策略都以相同的方式处理相同的工作
结构
├── main.go
└── obie
├── access_token.go
├── client.go
├── config.go
└── refresh_token.go
文件
main.go
package main
import (
"context"
"net/http"
"github.com/you/obie"
)
func main() {
barclays := obie.ProviderConfig{
TokenEndpoint: "https://barclays/token/endpoint",
ClientID: "barclays-client",
ClientSecret: "barclays-secret",
}
natwest := obie.ProviderConfig{
TokenEndpoint: "https://natwest/token/endpoint",
ClientID: "natwest-client",
ClientSecret: "natwest-secret",
}
config := &obie.Config{
Providers: map[obie.ProviderName]obie.ProviderConfig{
obie.ProviderName("barclays"): barclays,
obie.ProviderName("natwest"): natwest,
},
}
ctx := context.Background()
client := obie.NewClient(config, http.DefaultTransport)
_, _ = client.AccessToken(ctx, obie.AccessTokenRequest{ProviderName: "barclays"})
_, _ = client.RefreshToken(ctx, obie.RefreshTokenRequest{ProviderName: "natwest", RefreshToken: "ref-tok"})
}
obie/config.go
package obie
type ProviderName string
type Config struct {
Providers map[ProviderName]ProviderConfig
}
type ProviderConfig struct {
TokenEndpoint string
ClientID string
ClientSecret string
}
obie/client.go
package obie
import (
"context"
"net/http"
)
type Client interface {
AccessToken(context.Context, AccessTokenRequest) (AccessToken, error)
RefreshToken(context.Context, RefreshTokenRequest) (AccessToken, error)
}
type ClientStrategy struct {
config *Config
transport http.RoundTripper
}
func NewClient(config *Config, transport http.RoundTripper) ClientStrategy {
return ClientStrategy{
config: config,
transport: transport,
}
}
欧比/access_token.go
package obie
import (
"bytes"
"context"
"fmt"
"net/http"
"net/http/httputil"
"net/url"
)
type AccessTokenRequest struct {
ProviderName ProviderName
}
type AccessToken struct {
AccessToken string
RefreshToken string
TokenType string
Scope string
ExpiresIn int
}
func (c ClientStrategy) AccessToken(ctx context.Context, req AccessTokenRequest) (AccessToken, error) {
config := c.config.Providers[req.ProviderName]
data := url.Values{}
data.Set("grant_type", "client_credentials")
data.Set("client_id", config.ClientID)
data.Set("client_secret", config.ClientSecret)
httpReq, _ := http.NewRequestWithContext(
ctx,
http.MethodPost,
config.TokenEndpoint,
bytes.NewReader([]byte(data.Encode())),
)
httpReq.Header.Set("Cache-Control", "no-store")
httpReq.Header.Set("Content-Type", "application/x-www-form-urlencoded")
httpReq.Header.Set("Pragma", "no-cache")
body, _ := httputil.DumpRequest(httpReq, true)
fmt.Println(string(body))
// Send the request and handle the response
// c.transport.RoundTrip(httpReq)
return AccessToken{}, nil
}
欧比/refresh_token.go
package obie
import (
"bytes"
"context"
"fmt"
"net/http"
"net/http/httputil"
"net/url"
)
type RefreshTokenRequest struct {
ProviderName ProviderName
RefreshToken string
}
func (c ClientStrategy) RefreshToken(ctx context.Context, req RefreshTokenRequest) (AccessToken, error) {
config := c.config.Providers[req.ProviderName]
data := url.Values{}
data.Set("grant_type", "refresh_token")
data.Set("client_id", config.ClientID)
data.Set("client_secret", config.ClientSecret)
data.Set("refresh_token", req.RefreshToken)
httpReq, _ := http.NewRequestWithContext(
ctx,
http.MethodPost,
config.TokenEndpoint,
bytes.NewReader([]byte(data.Encode())),
)
httpReq.Header.Set("Cache-Control", "no-store")
httpReq.Header.Set("Content-Type", "application/x-www-form-urlencoded")
httpReq.Header.Set("Pragma", "no-cache")
body, _ := httputil.DumpRequest(httpReq, true)
fmt.Println(string(body))
// Send the request and handle the response
// c.transport.RoundTrip(httpReq)
return AccessToken{}, nil
}