关于c++17:谈-C17-里的-Strategy-模式

41次阅读

共计 3312 个字符,预计需要花费 9 分钟才能阅读完成。

策略模式

Strategy Pattern

在地图上对两点进行路线布局就是一种典型的策略模式利用场景。当咱们进行终点到起点的路线布局时,咱们期待地图给出这些形式的最佳路线:步行。公交,驾车。有时候可能细分为公交(轨道交通优先),公交(换乘优先)等若干策略。

规范实作

依照咱们的结构习惯,上面是门路布局的一个框架性代码

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);
}

除了下面的测试代码局部那样的写法之外,咱们还能够征引工厂模式来创立所有 router 的实例,并且枚举全副 routers 来一次性地失去所有门路布局。这种遍历的形式也是工程上实在会采纳的计划,例如地图软件中总是这么治理所有的可能的 路由器 的。

整顿

依据下面的示例,咱们能够重新整理出策略模式的若干要点:

  1. 策略模式是从一堆办法中形象出实现特定工作的公共接口,根据该公共接口,提供一个管理者,以及若干策略。
  2. 每一策略代表着实现特定工作的不同算法。
  3. 管理者不关怀具体采纳的策略有何特别之处,只有它反对公共的策略计算接口就行。
  4. 管理者负责提供一个上下文环境来调用策略计算器。
  5. 上下文环境可能为策略计算带来不同的计算环境。
  6. 策略计算器依据本人的算法须要从上下文环境中抽取感兴趣的参数,籍以实现计算
  7. 计算结果被形象为一个公共类的状态。
  8. 策略计算器能够在公共后果类的根底上派生出本人的非凡实现,但为了管理者可能抽取后果,此时要约定一个私有的抽取后果的接口。

在示例代码中提供了一个模板类的中间层 router_t<T>,咱们正是打算在这个地位为 routers 引入工厂创立和注册机制,以便可能在一个管理器中收集全副 routers 的惟一实例,稍后能够在 router_guide 中应用它们。

Policy-based Programming

此前我在 C++ policies & traits 中已经议论过面向 policy 的元编程手法。

面向 Policy 编程,和策略模式有些相似之处,也有不同之处。

例如抉择不同笔型的写作器:

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();}

这是用面向 Policy 编程手法实作的策略模式的一个例子。它有这样的轻微之处值得注意:

  1. 没有 strategy 的公共基类了

    不像前文的 router 的基类来提供一个策略操作接口,元编程的世界里能够借助于 SFINAE 的技巧间接做接口耦合。

  2. 因为模板的编译期开展的特点,因而在运行期动静切换策略变得较为不可行。

    为了做到运行期动静切换,你可能须要特地附加若干代码来提供数个 writers,它们别离对不同的笔型进行开展,以备运行期可用。

可能失当的场合

依照通常的了解,你或者会感觉前一种办法才是规范的策略模式。而且,怎么会有什么场景须要我抉择一种策略在编译期就固化下来呢?

事实上还真是有。

在下面抉择笔型作为示例,只是为了较为精简地演示出代码编写办法(而且它是咱们上一篇文章中的案例),但它的确不是编译期策略抉择的最佳例子。

然而构想这样的状况,你是一个类库作者,正在提供一个通用型的 socket 通信库。那么对于阻塞式和非阻塞式就能够提供两个 policy class 别离予以实现。而用户在应用你的 socket 库时能够依据他的通信场景抉择一个最失当的 policy:

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(){// ...}
};

在这个构型中,通过抉择通信模式为阻塞或非阻塞,同样也将实现了策略模式,但它就是适宜于编译期做出的抉择。

相似的,你还能够在应用这个通信库时抉择时 TCP 还是 UDP 通信,数据报的编码解码采纳何种算法等等,这些都能够通过 socket_man 的模板参数以 policy 的形式实现策略模式的抉择。

后记

文中提供了两种典型的实作办法,别离代表着策略模式的编译期开展和运行期开展计划。

依据理论的场景你能够参考并筛选一种。

策略模式的工程上的利用还能够有很多种状态,甚至不用局限于编码、特定语言的编码层面中。例如咱们还能够通过插件构造的形式来提供二进制层面的策略抉择。

另:这一次倒是没用到 C++17 的专有个性。奈何咱们这题目须要系列化嘛。

正文完
 0