【问题标题】:Meaning of decoupling representation from interface (c++)从接口解耦表示的含义(c++)
【发布时间】:2015-03-03 10:15:39
【问题描述】:

我正在阅读 Stroustrup 的 C++ 编程第 2 章。当他从具体类型过渡到抽象类型时,他提到具体与表示不耦合。因此,如果 Stacks 类发生重大变化,用户必须重新编译。

但我看不出与 Stacks 是抽象的情况有什么不同,他以相同的方式使用派生类。那么接口的解耦到底有什么作用呢?为什么在某些或大多数情况下它是可取的?

编辑:本书为《c++编程语言》特别版(2000)。第 2 章,p5.4。对不起。

【问题讨论】:

  • 很抱歉我是个顽固分子,但我找不到同名的书。到底是哪一个?
  • @KristianDuske 这个stroustrup.com/4th.html
  • @KristianDuske “我认为”
  • 你能举个例子吗?
  • @jiggunjer 如果您可以提供一些信息,我们可以在其中找到您所指的部分,这也会很有用。这本书的哪个版本和哪一页?

标签: c++ terminology


【解决方案1】:

接口定义了实现必须支持的逻辑操作,以让客户端代码访问某些功能。例如,支持输出操作的抽象类型可能是:

struct Abstract_Output
{
    virtual void blocking_write(const char* p, size_t n) = 0;
};

许多不同的输出设备可以有自己的实现来满足该接口。例如,一个最小的低级 TCP 库可能会在它至少发送部分消息后报告——告诉你写入了多少字节——但可能不支持自动重试,直到所有特定数量的字节都已传输完毕,不管需要多长时间。实现可能如下所示:

struct TCP_Output : Abstract_Output
{
    TCP_Output(const char* server_name, int port) : tcp_(server_name, port) { }

    void blocking_write(const char* p, size_t n) override
    {
        size_t bytes_written = 0;
        while (n && (bytes_written = tcp_.write(p, n)) > 0)
        {
            p += bytes_written;
            n -= bytes_written;
        }
        if (n > 0) throw std::runtime_error("incomplete TCP write");
    }
  private:
    TCP tcp_;
};

另一方面,如果您正在写入 std::ostream 对象,它将阻塞直到写入请求的确切字节数,因此我们可以编写:

struct Stream_Output : Abstract_Output
{
    Stream_Output(std::ostream& os) : os_(os) { }

    void blocking_write(const char* p, size_t n) override
    {
        os_.write(p, n_);
    }
};

然后,您可以通过抽象类/结构使用运行时多态性编写可与任何类型的输出对象一起使用的函数:

void report(Abstract_Output& o)
{
    std::ostringstream oss("/--- REPORT --/\n");
    for (auto& x : stocks)
        oss << x << '\n';
    o.blocking_write(oss.str().c_str(), oss.str().data());
}

然后可以通过任何实现调用:

Stream_Output stream_output(std::cout);
report(stream_output); // report to std::cout
TCP_Output tcp_output("localhost", 9191);
report(tcp_output);  // write report to the TCP server listening on port :9191

将以上所有内容与您的问题联系起来:

当他从具体类型转换到抽象类型时,他提到具体与表示不耦合。因此,如果 Stacks 类发生重大变化,用户必须重新编译。

[[ 请同时引用他的确切文字,然后我们可以看看您是否误解了它。 ]] 我们通过使用抽象接口实现的目的是不将report 之类的函数与TCP_OutputStream_Output 之类的具体输出实现耦合。 report 之类的函数可以放在它们自己的头文件/实现文件中,如果某处的某些客户端代码想要使用不同的Abstract_Output 派生的具体输出实现来调用它们,则不需要重新编译。

但我看不出与 Stacks 是抽象的情况有什么不同,他以相同的方式使用派生类。那么接口的解耦到底有什么作用呢?为什么在某些或大多数情况下它是可取的?

所以 - 如上所述,只有抽象类需要被提供report 等功能的翻译单元看到。此外,report 可用于(在重新链接后)向输出设备发送报告,这在编写report 函数时甚至未曾设想过,更不用说实现了。这就是解耦。

【讨论】:

  • 谢谢,我想这已经很清楚了。但是解耦是通过继承实现的,而不是虚函数。因此,即使 Abstract_output 具有正常的虚函数,您的示例仍应适用于报告函数?所以看起来虚函数只是强制程序员使用一致的名称,但解耦来自继承,而不是抽象类型本身?
  • 你也不能让报告接受模板的通用输入,完全放弃继承的需要吗?
  • @jiggunjer:继承本身并不能让report 函数对派生类型进行操作——这只能是因为virtual 函数可用于调用派生类实现。试试看,自己看看。使用模板时使用一致的名称就足够了(即参数多态性/鸭子类型),但是如果您不理解运行时多态性有时比编译时更可取的原因,您应该做更多的背景阅读。如果您愿意,请谷歌,或尝试我的“最佳”答案以获得概述......
猜你喜欢
  • 1970-01-01
  • 2018-07-21
  • 1970-01-01
  • 2015-01-30
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-11-01
  • 1970-01-01
相关资源
最近更新 更多