Strategy Pattern

Route planning for two points on a map is a typical strategy mode application scenario. When we do a start to finish route planning, we expect the map to give us the best route for these modes: walking. Public transit, driving. Sometimes it may be subdivided into several strategies such as transit (rail priority), bus (transfer priority), etc.

Standard work

Following our construction convention, here is a framework code for path planning

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
namespace hicc::dp::strategy::basic {

    struct guide {};
    struct context {};

    class router {
    public:
        virtual ~router() {}
        virtual guide make_guide(context &ctx) = 0;
    };

    template<typename T>
    class router_t : public router {
    public:
        virtual ~router_t() {}
    };

    class by_walk : public router_t<by_walk> {
    public:
        virtual ~by_walk() {}
        guide make_guide(context &ctx) override {
            guide g;
            UNUSED(ctx);
            return g;
        }
    };

    class by_transit : public router_t<by_transit> {
    public:
        virtual ~by_transit() {}
        guide make_guide(context &ctx) override {
            guide g;
            UNUSED(ctx);
            return g;
        }
    };

    class by_drive : public router_t<by_drive> {
    public:
        virtual ~by_drive() {}
        guide make_guide(context &ctx) override {
            guide g;
            UNUSED(ctx);
            return g;
        }
    };

    class route_guide {
    public:
        void guide_it(router &strategy) {
            context ctx;
            guide g = strategy.make_guide(ctx);
            print(g);
        }

    protected:
        void print(guide &g) {
            UNUSED(g);
        }
    };

} // namespace hicc::dp::strategy::basic

void test_strategy_basic() {
    using namespace hicc::dp::strategy::basic;
    route_guide rg;

    by_walk s;
    rg.guide_it(s);
}

In addition to writing it like the test code above, we can also invoke the factory pattern to create all instances of the router and enumerate all routers to get all path plans at once. This iterative approach is also a real-life engineering solution, as it is always used in mapping software to manage all possible routers.

Sorting

Based on the above example, we can reorganize a number of key points of the strategy pattern.

  1. the strategy pattern abstracts a public interface for accomplishing a specific task from a bunch of methods, provides a manager based on that public interface, and a number of strategies.
  2. each strategy represents a different algorithm for achieving a particular task.
  3. the manager does not care what is special about the specific policy used, as long as it supports the public policy computing interface.
  4. The manager is responsible for providing a contextual environment to invoke the policy calculator.
  5. The contextual environment can bring a different computational environment to the policy calculator.
  6. the policy calculator extracts the parameters of interest from the contextual environment according to the needs of its algorithm in order to complete the computation
  7. The result of the computation is abstracted into a common class form.
  8. The policy calculator can derive its own special implementation based on the public result class, but in order for the manager to extract the result, a public interface for extracting the result is agreed upon.

The example code provides an intermediate layer of the template class router_t<T>, and it is at this location that we intend to introduce a factory creation and registration mechanism for routers, so that we can collect all unique instances of routers in a single manager and use them later in router_guide.

Policy-based Programming

Policy-oriented programming, which has some similarities and differences with the policy pattern.

For example, to select different pen types of writers.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
struct InkPen {
    void Write() {
        this->WriteImplementation();
    }

    void WriteImplementation() {
        std::cout << "Writing using a inkpen" << std::endl;
    }
};

struct BoldPen {
    void Write() {
        std::cout << "Writing using a boldpen" << std::endl;
    }
};

template<class PenPolicy>
class Writer : private PenPolicy {
public:
    void StartWriting() {
        PenPolicy::Write();
    }
};

void test_policy_1() {
    Writer<InkPen> writer;
    writer.StartWriting();
    Writer<BoldPen> writer1;
    writer1.StartWriting();
}

This is an example of a policy pattern implemented with Policy-oriented programming techniques. It has the following subtleties that are worth noting: 1.

  1. there is no public base class for strategy unlike the base class for router in the previous section to provide an interface for policy operations, the metaprogramming world can do interface coupling directly with the help of SFINAE techniques.
  2. Because of the compile-time unfolding feature of the template, it becomes less feasible to switch strategies dynamically at runtime. In order to do dynamic switching at runtime, you may need to provide several writers with additional code, which are expanded for different pen types, for use at runtime.

Possible appropriate sites

As commonly understood, you might think that the former approach is the standard strategy pattern. And, how can there be any scenario where I need to choose a strategy to be solidified at compile time?

Well, there is.

The pen type was chosen above as an example just to demonstrate the code writing approach in a more concise way (and it was the case in our previous article), but it really is not the best example of compile-time strategy selection.

But imagine a situation where you are a class library author and are providing a general-purpose socket communication library. Then you can provide two policy classes for blocking and non-blocking, and implement them separately. And the user can choose the most appropriate policy for his communication scenario when using your socket library.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
class non_blocked {
  public:
  void connect(){}
  void disconnect(){}
  
};

class blocked {
  public:
  void connect(){}
  void disconnect(){}
};

struct tcp_conn;
struct udp_conn;

template<typename DiagramMode = tcp_conn,
         typename CommunicateMode = non_blocked,
         typename Codec = packaged_codec>
class socket_man {
  public:
  void connect(){}
  void disconnect(){}
  
  protected:
  virtual void on_connect(...);
  virtual void on_recv(...);
  virtual void on_send(...);
  
  protected:
  static void runner_routine(){
    // ...
  }
};

In this configuration, the policy mode is also implemented by selecting the communication mode as blocking or non-blocking, but it is the compile-time choice that is appropriate.

Similarly, you can choose whether to use TCP or UDP communication, which algorithm to use for encoding and decoding datagrams, and so on when using this communication library.

Postscript

Two typical implementation methods are provided in the paper, representing compile-time and run-time deployment schemes for the policy pattern.

Depending on the actual scenario you can refer to and pick one.

The engineering application of policy patterns can take many other forms, and does not even have to be limited to the coding, language-specific coding level. For example, we can also provide binary-level policy options by way of plug-in structures.