60 | 架构分解:边界,不断重新审视边界
IO 子系统的需求与初始架构
接口是业务的抽象,同时也是它与使用方的耦合方式。在业务分解的过程中,我们需要认真审视模块的接口,发现其中 “过度的(或多余的)” 约束条件,把它提高到足够通用的、普适的场景来看。
IO DOM 模式
type IoSpan interface {
Text() []byte
Attributes() IoSpanAttrs
}
type IoSpans interface {
Len() int
Elem(i int) IoSpan
}
type IoParagraph interface {
Spans() IoSpans
Attributes() IoParagraphAttrs
}
type IoParagraphs interface {
Len() int
Elem(i int) IoParagraph
}
type IoDocument interface {
Paragraphs() IoParagraphs
Attributes() IoDocumentAttrs
}
func NewIoDocument() IoDocument
type Document struct {
...
Io() IoDocument
}
func NewDocument() *Document
func SaveWord(stg IStorage, doc IoDocument) error
func SaveRTF(f *os.File, doc IoDocument) error
func SaveFile(file string, format string, doc IoDocument) error
func LoadWord(stg IStorage, doc IoDocument) error
func LoadRTF(f *os.File, doc IoDocument) error
func LoadFile(file string, doc IoDocument) error
在这个架构,我们认为有两套 DOM
一套是 IO DOM
,即 IoDocument
接口及其相关的接口。
一套是核心系统自己的 DOM
,也就是 Document
类及其相关的接口
RTF 文件转为 Word 文件
func ConvRTF2Word(rtf *os.File, word IStorage) error {
doc := NewIoDocument()
err := LoadRTF(rtf, doc)
if err != nil {
return err
}
return SaveWord(word, doc)
}
加载一个 Word 文件
func LoadWordDocument(stg IStorage) (*Document, error) {
doc := NewDocument()
err := LoadWord(stg, doc.Io())
if err != nil {
return nil, err
}
return doc, nil
}
IO
子系统在特定的场景下,其实与排版与绘制子系统相关,包括:
- 屏幕绘制(onPaint);
- 打印(onPrint)。
通过排版(Render)功能,可以渲染出 View 层所需的显示数据,我们不妨称之为 View DOM。
func Render(doc IoDocument) (ViewDocument, error)
func SavePDF(f *os.File, doc ViewDocument) error
func SavePS(f *os.File, doc ViewDocument) error
不断重新审视边界
io.Reader/Writer
来表示
func SaveRTF(f *os.File, doc IoDocument) error
func LoadRTF(f *os.File, doc IoDocument) error
func SavePDF(f *os.File, doc ViewDocument) error
func SavePS(f *os.File, doc ViewDocument) error
改编为
func SaveRTF(f io.Writer, doc IoDocument) error
func LoadRTF(f io.Reader, doc IoDocument) error
func SavePDF(f io.Writer, doc ViewDocument) error
func SavePS(f io.Writer, doc ViewDocument) error
在我们模块提高到足够通用的、普适的场景来看时,实际上并不需要剪贴板这样具体的用户场景,也能够及时地发现这种过度约束。
我们的 IO 子系统的入口级的接口:
func SaveFile(file string, format string, doc IoDocument) error
func LoadFile(file string, doc IoDocument) error
其一,LoadFile
方法我们可能希望知道加载的文件具体是文档格式,所以应该改为
func LoadFile(file string, doc IoDocument) (format string, err error)
我们输入的数据源不一定是文件,还可能是 io.Reader
、IStorage
等,在 Windows 平台下有 STGMEDIUM
结构体来表达通用的介质类型,可以参考。从跨平台的角度,也可以考虑直接用 Go 语言中的任意类型。如下:
func Save(src interface{}, format string, doc IoDocument) error
func Load(src interface{}, doc IoDocument) (format string, err error)
用了 interface{}
这样的任意类型,就意味着我们需要在文档层面上补充清楚我们都支持些什么,不支持些什么,避免在团队共识上遇到麻烦。
考虑 PDF/PS
这类非流式文档的支持,我们不能用 IoDocument
作为输入文档的类型。也就是说,以下接口:
func Save(dest interface{}, format string, doc IoDocument) error

本作品采用知识共享署名-非商业性使用-禁止演绎 4.0 国际许可协议进行许可。
- 上一篇: 59 | 少谈点框架,多谈点业务
- 下一篇: 61 | 全局性功能的架构设计
目录