假设我需要编写一个将 Document
结构保存到磁盘的函数的任务。
// Save writes the contents of doc to the file f.
func Save(f *os.File, doc *Document) error
我可以指定这个函数 Save
,它将 *os.File
作为写入 Document
的目标。但这样做会有一些问题
Save
的签名排除了将数据写入网络位置的选项。假设网络存储可能在以后成为需求,则此功能的签名必须改变,从而影响其所有调用者。
Save
测试起来也很麻烦,因为它直接操作磁盘上的文件。因此,为了验证其操作,测试时必须在写入文件后再读取该文件的内容。
而且我必须确保 f
被写入临时位置并且随后要将其删除。
*os.File
还定义了许多与 Save
无关的方法,比如读取目录并检查路径是否是符号链接。 如果 Save
函数的签名只用 *os.File
的相关内容,那将会很有用。
我们能做什么 ?
// Save writes the contents of doc to the supplied
// ReadWriterCloser.
func Save(rwc io.ReadWriteCloser, doc *Document) error
使用 io.ReadWriteCloser
,我们可以应用接口隔离原则来重新定义 Save
以获取更通用文件形式。
通过此更改,任何实现 io.ReadWriteCloser
接口的类型都可以替换以前的 *os.File
。
这使 Save
在其应用程序中更广泛,并向 Save
的调用者阐明 *os.File
类型的哪些方法与其操作有关。
而且,Save
的作者也不可以在 *os.File
上调用那些不相关的方法,因为它隐藏在 io.ReadWriteCloser
接口后面。
但我们可以进一步采用接口隔离原则。
首先,如果 Save
遵循单一功能原则,它不可能读取它刚刚写入的文件来验证其内容 - 这应该是另一段代码的功能。
// Save writes the contents of doc to the supplied
// WriteCloser.
func Save(wc io.WriteCloser, doc *Document) error
因此,我们可以将我们传递给 Save
的接口的规范缩小到只写和关闭。
其次,通过向 Save
提供一个关闭其流的机制,使其看起来仍然像一个文件,这就提出了在什么情况下关闭 wc
的问题。
可能 Save
会无条件地调用 Close
,或者在成功的情况下调用 Close
。
这给 Save
的调用者带来了问题,因为它可能希望在写入文档后将其他数据写入流。
// Save writes the contents of doc to the supplied
// Writer.
func Save(w io.Writer, doc *Document) error
一个更好的解决方案是重新定义 Save
仅使用 io.Writer
,它只负责将数据写入流。
将接口隔离原则应用于我们的 Save
功能,同时, 就需求而言, 得出了最具体的一个函数 - 它只需要一个可写的东西 - 并且它的功能最通用,现在我们可以使用 Save
将我们的数据保存到实现 io.Writer
的任何事物中。
[译注: 不理解设计原则部分的同学可以阅读 Dave 大神的另一篇《Go 语言 SOLID 设计》]