我正在阅读Stroustrup用c ++编程的第2章。当他从具体类型转换为抽象类型时,他提到具体不与表示相联系。因此,如果类Stacks以显着的方式发生变化,则用户必须重新编译。
但是我没有看到Stacks是抽象的情况与他以相同方式使用派生类的情况有所不同。那么接口的解耦实际上做了什么呢?为什么在某些情况下或大多数情况下都可取?
编辑:这本书是" c ++编程语言"特别版(2000年)。第2章,第5.4页。遗憾。
答案 0 :(得分:3)
接口定义了实现必须支持的逻辑操作,以允许客户端代码访问某些功能。例如,支持输出操作的抽象类型可能是:
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_Output
和Stream_Output
。像report
这样的函数可以放在它们自己的头文件/实现文件中,如果某个客户端代码想要用不同的Abstract_Output
派生的具体输出实现调用它们,则不需要重新编译。
但是我没有看到Stacks是抽象的情况与他以相同方式使用派生类的情况有所不同。那么接口的解耦实际上做了什么呢?为什么在某些情况下或大多数情况下都可取?
所以 - 如上所述,翻译单元只需要提供抽象类来提供report
等功能。此外,可以使用report
(在重新链接之后)将报告发送到甚至未设想的输出设备,更不用说在编写report
函数时实现。那是脱钩。