The strategy pattern

In the Strategy Pattern, the behavior of a class or its algorithm can be changed at run time. This type of design pattern is behavioral.

In the policy pattern, we create objects that represent various policies and a context object whose behavior changes as the policy object changes. The policy object changes the execution algorithm of the context object.

Intent: Define a set of algorithms, encapsulate them one by one, and make them interchangeable.

In the case of similar algorithms, if… Else is complex and difficult to maintain.

When to use: A system has many, many classes that are distinguished only by their immediate behavior.

How to solve it: Encapsulate these algorithms into classes and replace them arbitrarily. (Go doesn’t even encapsulate classes.)

Key code: implement the same interface.

Advantages:

  1. Algorithms can be switched freely.
  2. Avoid using multiple conditional judgments.
  3. Good scalability.

Disadvantages:

  1. Policy classes will increase.
  2. All policy classes need to be exposed.

Usage Scenarios:

  1. If there are many classes in a system that are distinguished only by their behavior, then using the policy pattern dynamically allows an object to choose one behavior among many behaviors.
  2. A system needs to dynamically choose one of several algorithms.
  3. If an object has many behaviors, those behaviors can be implemented using multiple conditional selection statements without proper patterns

First class citizens

In Go language, functions as first-class citizens have the following characteristics:

  1. The function allows multiple return values, which is different from the current mainstream C/C++, Java.
  2. The function itself can be passed as a value
  3. A function can be the value of a variable
  4. Functions can be arguments and return values
  5. Support for anonymous functions (functions without names) is generally used in factory mode.
  6. Can satisfy the interface.

The code examples

Suppose we now have a custom structure with multiple fields.

In different scenarios, we need to sort by different fields.

Combining the characteristics of policy pattern and first-class citizen function, we can write the following code:

The structure of the body

First, we define a structure that needs to be sorted:

type earthMass float64
type au float64

// Planet defines the properties of the planets in our solar system.
type Planet struct {
        name     string
        mass     earthMass
        distance au
}
Copy the code

We then define a structure that encapsulates the Planet and sorting algorithm:

// planetSorter encapsulates a By() function and a set of planets to sort.
type planetSorter struct {
        planets []Planet
        by func(p1, p2 *Planet) bool // Less(a)methods
}
Copy the code

Sort the template

Since the function is a first-class citizen, we can declare the function as a new type and add a sorting algorithm to that type.

The sorting algorithm is just a template, and the exact sort implementation depends on the By object.

// By is the type of the Less() function that defines the order of Planet arguments.
type By func(p1, p2 *Planet) bool

// Sort() is a method of function type By that sorts the argument slices By function.
func (by By) Sort(planets []Planet) {
        ps := &planetSorter{
                planets: planets,
                by: by, // The receiver of the Sort() method is a function that defines the Sort order.
        }
        sort.Sort(ps)
}
Copy the code

Sorting interface

Because the sort function in the sort package is used to implement the sort.interface Interface.

We implement Len(), Swap(), and Less() methods for the planetSorter structure.

Note: The Less() method is implemented by calling the BY () function.

func (s *planetSorter) Len(a) int {
        return len(s.planets)
}

func (s *planetSorter) Swap(i, j int) {
        s.planets[i], s.planets[j] = s.planets[j], s.planets[i]
}

// By calling the "by" function in the classifier.
func (s *planetSorter) Less(i, j int) bool {
        return s.by(&s.planets[i], &s.planets[j])
}
Copy the code

use

var planets = []Planet{
        {"Mercury".0.055.0.4},
        {"Venus".0.815.0.7},
        {"Earth".1.0.1.0},
        {"Mars".0.107.1.5}},// demonstrates a technique for sorting structural types using programmable sorting criteria.
func main(a) {
        // A function to sort Planet.
        name := func(p1, p2 *Planet) bool {
                return p1.name < p2.name
        }
        mass := func(p1, p2 *Planet) bool {
                return p1.mass < p2.mass
        }
        distance := func(p1, p2 *Planet) bool {
                return p1.distance < p2.distance
        }
        decreasingDistance := func(p1, p2 *Planet) bool {
                return distance(p2, p1)
        }

        // Sort planets according to different criteria.
        By(name).Sort(planets)
        fmt.Println("By name:", planets)

        By(mass).Sort(planets)
        fmt.Println("By mass:", planets)

        By(distance).Sort(planets)
        fmt.Println("By distance:", planets)

        By(decreasingDistance).Sort(planets)
        fmt.Println("By decreasing distance:", planets)

        / / output:
	// By name: [{Earth 1 1} {Mars 0.107 1.5} {Mercury 0.055 0.4} {Venus 0.815 0.7}]
        // By mass: [{Mercury 0.055 0.4} {Mars 0.107 1.5} {Venus 0.815 0.7} {Earth 1 1}]
        // By distance: [{Mercury 0.055 0.4} {Venus 0.815 0.7} {Earth 1 1} {Mars 0.107 1.5}]
        // By decreasing distance: [{Mars 0.107 1.5} {Earth 1 1} {Venus 0.815 0.7} {Mercury 0.055 0.4}]
}
Copy the code