NOTE

Strategy Pattern

The strategy pattern selects one of several algorithms for accomplishing the same task according to a type or context, with Java and Go examples.

Software Architecture & EngineeringCreated Updated 1 min readhistorical

This is a historical learning note and may contain outdated or incomplete understanding.

1. Definition

There are multiple different ways to accomplish the same thing. Select a different algorithm according to the type; using if/else is too cumbersome.

The difference from the state pattern is that every strategy in the strategy pattern is used to accomplish the same thing, while the state pattern is used to accomplish different things.

2. UML

PlantUML 图表

3. Java

3.1. Client

public class Client
{
    public static void main(String[] args)
    {
        double price = 100.0;

        StrategyContext context = new StrategyContext();

        // Existing customer, small amount
        context.setStrategy(new OldCustomerFew());
        context.getPrice(price);

        // New customer, small amount
        context.setStrategy(new NewCustomerFewer());
        context.getPrice(price);
    }
}

3.2. Context

public class StrategyContext
{
    private Strategy strategy;

    public Strategy getStrategy()
    {
        return strategy;
    }

    public void setStrategy(Strategy strategy)
    {
        this.strategy = strategy;
    }

    public void getPrice(double price)
    {
        System.out.println(this.strategy.getPrice(price));
    }
}

3.3. Strategy

public interface Strategy
{
    double getPrice(double price);
}
public class NewCustomerFewer implements Strategy
{
    @Override
    public double getPrice(double price)
    {
        return price * 0.95;
    }
}
public class NewCustomerMany implements Strategy
{
    @Override
    public double getPrice(double price)
    {
        return 0.9 * price;
    }
}
public class OldCustomerFew implements Strategy
{
    @Override
    public double getPrice(double price)
    {
        return price * 0.85;
    }
}

4. Golang

4.1. Strategy

/* User discount strategy */
type IStrategy interface {
	GetPrice(price float64) float64
}

// Strategy with a larger discount for existing users
type OldCustomerMuchStrategy struct {
}

func NewOldCustomerMuch() *OldCustomerMuchStrategy {
	return &OldCustomerMuchStrategy{}
}

func (o OldCustomerMuchStrategy) GetPrice(price float64) float64 {
	return price * 0.5
}

// Strategy with a smaller discount for new users
type NewCustomerMuchStrategy struct {
}

func NewNewCustomerFew() *NewCustomerMuchStrategy {
	return &NewCustomerMuchStrategy{}
}

func (o NewCustomerMuchStrategy) GetPrice(price float64) float64 {
	return price * 0.8
}

4.2. Strategy Manager

/* Strategy manager */
type StrategyContext struct {
	strategy IStrategy
}

func NewStrategyContext() *StrategyContext {
	return &StrategyContext{}
}

func (s *StrategyContext) Strategy() IStrategy {
	return s.strategy
}

func (s *StrategyContext) SetStrategy(strategy IStrategy) {
	s.strategy = strategy
}

// Apply the discount
func (s *StrategyContext) GetPrice(price float64)  {
	fmt.Printf("Discounted price: %f
", s.strategy.GetPrice(price))
}

4.3. Client

func main() {
	price := 100.0
	strategyContext := 策略模式.NewStrategyContext()
	strategyContext.SetStrategy(策略模式.NewOldCustomerMuch())
	strategyContext.GetPrice(price)
	strategyContext.SetStrategy(策略模式.NewNewCustomerFew())
	strategyContext.GetPrice(price)
}

5. Example

Template method + simple factory + strategy pattern.

  • pb.go
package pb

type SwitchRoomReq struct {
}

type SwitchRoomRsp struct {
}
  • abstract_template.go
package template

import (
	"context"
	"fmt"
	"test/template/pb"
)

/* Implements IFetchDataService */
type abstractFetchDataService struct {
	IFetchDataSteps
	req       *pb.SwitchRoomReq
	rsp       *pb.SwitchRoomRsp
	basicInfo interface{}
	extraInfo interface{}
}

func (t *abstractFetchDataService) checkParam(ctx context.Context) error {
	fmt.Println("default checkParam")
	return nil
}

func (t *abstractFetchDataService) getBasicInfo(ctx context.Context) error {
	fmt.Println("default getBasicInfo")
	return nil
}

func (t *abstractFetchDataService) getExtraInfo(ctx context.Context) error {
	fmt.Println("default getExtraInfo")
	return nil
}

func newTemplate(iSteps IFetchDataSteps) *abstractFetchDataService {
	return &abstractFetchDataService{IFetchDataSteps: iSteps}
}

func (t *abstractFetchDataService) HandleRequest(ctx context.Context, req *pb.SwitchRoomReq, rsp *pb.SwitchRoomRsp) error {
	t.req = req
	t.rsp = rsp
	if err := t.IFetchDataSteps.checkParam(ctx); err != nil {
		return err
	}
	if err := t.IFetchDataSteps.getBasicInfo(ctx); err != nil {
		return err
	}
	if err := t.IFetchDataSteps.getExtraInfo(ctx); err != nil {
		return err
	}
	return nil
}
  • content_read_template_impl.go
package template

import (
	"context"
	"fmt"
)

const TypeContentReadService = 2

var contentReadServiceConstructorFunc fetchDataServiceConstructor = func() IFetchDataService {
	return NewFetchDataFromContentReadService()
}

func init() {
	GlobalServiceFactory.RegisterService(TypeContentReadService, contentReadServiceConstructorFunc)
}

type FetchDataFromContentReadService struct {
	*abstractFetchDataService
}

func (f *FetchDataFromContentReadService) checkParam(ctx context.Context) error {
	fmt.Println("checkParam")
	return f.abstractFetchDataService.checkParam(ctx)
}

func (f *FetchDataFromContentReadService) getBasicInfo(ctx context.Context) error {
	fmt.Println("getBasicInfo")
	return f.abstractFetchDataService.getBasicInfo(ctx)
}

func NewFetchDataFromContentReadService() *FetchDataFromContentReadService {
	service := &FetchDataFromContentReadService{}
	service.abstractFetchDataService = newTemplate(service)
	return service
}
  • factory.go
package template

import "fmt"

var GlobalServiceFactory = newServiceFactory()

type fetchDataServiceConstructor = func() IFetchDataService

type serviceFactory struct {
	serviceMap map[int]fetchDataServiceConstructor
}

func newServiceFactory() *serviceFactory {
	return &serviceFactory{serviceMap: make(map[int]fetchDataServiceConstructor)}
}

func (s *serviceFactory) GetServiceByType(serviceType int) (IFetchDataService, error) {
	serviceConstructor, ok := s.serviceMap[serviceType]
	if ok {
		return serviceConstructor(), nil
	}
	err := fmt.Errorf("GetServiceByType: serviceType %v invalid", serviceType)
	return nil, err
}

func (s *serviceFactory) RegisterService(serviceType int, serviceConstructor fetchDataServiceConstructor) {
	s.serviceMap[serviceType] = serviceConstructor
}
  • itemplate.go
package template

import (
	"context"
	"test/template/pb"
)

type IFetchDataService interface {
	HandleRequest(ctx context.Context, req *pb.SwitchRoomReq, rsp *pb.SwitchRoomRsp) error
}

type IFetchDataSteps interface {
	checkParam(ctx context.Context) error
	getBasicInfo(ctx context.Context) error
	getExtraInfo(ctx context.Context) error
}
  • template_test.go
package template

import (
	"context"
	"testing"
)

func TestTemplate(t *testing.T) {
	service, _ := GlobalServiceFactory.GetServiceByType(TypeContentReadService)
	service.HandleRequest(context.Background(), nil, nil)
}
// Output
checkParam
default checkParam
getBasicInfo
default getBasicInfo
default getExtraInfo

Discussion

Sign in with GitHub to comment. Discussions are stored as GitHub Issues.View on GitHub