使用Golang的策略模式模型介绍

284 阅读3分钟

在这个例子中,我们将研究两种策略模式的实现,但它们都以稍微不同的方式实现了相同的结果。它们只是为不同的银行创建了代币。

这两种策略模式在现实世界的场景中都是有效的,所以 "重复的解决方案 "并不一定意味着它是一个糟糕的设计。这只是你如何处理这种商业案例。此外,两种模式都有其优势和劣势。

  • 所有的策略都以不同的方式处理相同的工作重复的解决方案,灵活的配置结构。由个别配置参数和具体策略管理。所有策略都可以有自己的配置结构。

  • 所有策略以相同的方式处理同一工作简单的解决方案,严格的配置结构。由配置参数管理,需要单一的具体策略。策略依赖于一种类型的配置结构。

这两个例子都依赖于请求对象中的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
}