接口定义了实现必须支持的逻辑操作,以让客户端代码访问某些功能。例如,支持输出操作的抽象类型可能是:
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 函数时甚至未曾设想过,更不用说实现了。这就是解耦。