优点& cons的回调(std :: function / std :: bind)vs接口(抽象类) [英] Pros & cons of a callback (std::function/std::bind) vs an interface (abstract class)

查看:163
本文介绍了优点& cons的回调(std :: function / std :: bind)vs接口(抽象类)的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我使用Boost.Asio在C ++ 11中创建一个服务器应用程序。我创建了一个类 Server ,它负责接受新的连接。它基本上只是:

  void Server :: Accept(){
socket_.reset(new boost :: asio: :ip :: tcp :: socket(* io_service_));
acceptor_.async_accept(* socket_,
boost :: bind(& Server :: HandleAccept,this,boost :: asio :: placeholders :: error));
}

void Server :: HandleAccept(const boost :: system :: error_code& error){
if(!error){
// TODO
} else {
TRACE_ERROR(Server :: HandleAccept:Error!);
}
Accept();
}



我找到了两种方法修复 TODO 注释,即将套接字移动到任何地方。在我的情况下,我只想让它回到类实例拥有服务器实例(然后将其包装在连接类并将其插入列表)。


  1. 服务器它的构造函数: std :: function< void(socket)>我创建了一个抽象类,
  2. $ IServerHandler 或其他,它有一个虚方法 OnAccept 服务器在其构造函数和拥有服务器实例的类实例中将 IServerHandler 作为参数extends IServerHandler 并以 * this 作为参数构建服务器

选项1和选项2的利弊是什么?有什么更好的选择吗?我在我的连接类( OnConnectionClosed )有同样的问题。此外,根据我如何决定设计系统,它可能需要 OnPacketReceived OnPacketSent 回调。

解决方案

我强烈喜欢第一种方式有几个原因:




  • 通过接口/类层次结构来表示概念/功能,使得代码库不那么通用,灵活,而且在将来更难以维护或扩展。这种设计对类型(实现所需功能的类型)强加了一组要求,这使得在将来难以修改,并且当系统改变时最容易失败(考虑当基类被修改时发生什么)


  • 您所呼叫的回呼方法只是鸭式输入的经典范例。服务器类只需要一个可实现所需功能的可调用事件,没有更多,没有更少。不需要条件,因此实现处理的类型完全免费

  • 预期的函数签名。这给予用户在实现处理程序时更自由。可以是全局函数,绑定成员函数,函子等。




以标准库为例:




  • 几乎所有标准库算法都基于迭代器范围。 C ++中没有 iterator 界面。迭代器只是实现迭代器行为的任何类型(即可解引用,可比较等)。迭代器类型是完全自由的,不同的和解耦的(不锁定到给定的类层次结构)。


  • 另一个例子可能是比较器:比较器是什么? 只是具有布尔比较函数签名的任何东西,可调用的函数需要两个参数,并返回一个布尔值,表示两个输入值是否相等(小于,大于等)的具体比较标准。 没有可比较的界面。



I'm creating a server application in C++11 using Boost.Asio. I've created a class, Server, which takes care of accepting new connections. It's basically just:

void Server::Accept() {
  socket_.reset(new boost::asio::ip::tcp::socket(*io_service_));
  acceptor_.async_accept(*socket_,
                         boost::bind(&Server::HandleAccept, this, boost::asio::placeholders::error));
}

void Server::HandleAccept(const boost::system::error_code& error) {
  if (!error) {
    // TODO
  } else {
    TRACE_ERROR("Server::HandleAccept: Error!");
  }
  Accept();
}

I've found two ways (I'm sure there are more) to "fix" the TODO comment, i.e. to move the socket to wherever it should go. In my case I just want it back to the class instance that owns the Server instance (which then wraps it in a Connection class and inserts it to a list).

  1. Server has a parameter in its constructor: std::function<void(socket)> OnAccept which is called in HandleAccept.
  2. I create an abstract class, IServerHandler or whatever, which has one virtual method OnAccept. Server takes IServerHandler as parameter in its constructor and the class instance owning the server instance extends IServerHandler and constructs Server with *this as parameter.

What are the pros and cons of option 1 vs option 2? Are there any better options? I'm having the same problem in my Connection class (OnConnectionClosed). Also, depending on how I decide to design the system, it might need a OnPacketReceived and OnPacketSent callback.

解决方案

I strongly prefer the first way for several reasons:

  • Representing concepts/functionality via interfaces/class hierarchies makes the code base less generic, flexible, and then more difficult to mantain or scale in the future. That kind of design imposes a set of requirements on the type (the type implementing the required functionality) which makes it difficult to modify in the future, and most prone to fail when the system changes (Consider what happens when the base class is modified in this type of designs).

  • What you called the callback approach is just the classic example of duck typing. The server class only expects a callable thing which implements the required functionality, nothing more, nothing less. No "your type must be coupled to this hierarchy" condition is required, so the type which implements handling is completely free.

  • Also, as I said the server only expects a callable thing: It could be anything with the expected function signature. This gives the user more freedom when implementing a handler. Could be a global function, a bound member function, a functor, etc.

Take the standard library as an example:

  • Almost all standard library algorithms are based on iterator ranges. There is no iterator interface in C++. An iterator is just any type which implements the behaviour of an iterator (Being dereferenceable, comparable, etc). Iterator types are completely free, distinct, and decoupled (Not locked to a given class hierarchy).

  • Another example could be comparators: Whats a comparator? Is just anything with the signature of a boolean comparison function, something callable which takes two parameters and returns a boolean value saying if the two input values are equal (less than, bigger than, etc) from the point of view of a specific comparison criteria. There is no Comparable interface.

这篇关于优点&amp; cons的回调(std :: function / std :: bind)vs接口(抽象类)的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆