Golang中的单一抽象层级原则(SLAP)示例详解
Golang中的单一抽象层级原则(SLAP)示例详解 大家好,
我有一个方法,它:
- 打开一个文件 F
- 读取其内容
- 从每一行 L 中解析出一些有用的数字和单词
- 创建一个复杂的数据结构 S
- 将每个解析出的数据 L 添加到 S 中
- 创建一个数据库实例
- 将复杂的数据结构写入数据库
- 返回数据结构和一个数据库连接
在我看来,这个方法违反了单一职责原则,因为它一次做了太多事情。应该重构它,使其成为一个控制器方法:它本身不知道如何做任何这些事情,而只是调用其他更小的、分别完成这些任务的方法。
因此,我打算重构它,让它只负责知道要调用哪些其他方法、正确的调用顺序以及进行错误处理。
但这个方法(原始形式,而非重构后的)还有更多我不喜欢的地方:一个构建数据结构的方法不应该知道如何解析文件的每一行,或者如何创建特定的数据库。解析一行文本(字符串)应该在另一层完成,读写数据库应该在另一层完成,等等。
我应该如何安排这种分层结构?对于如何将我的代码所调用的所有这些小方法组织成不同的层,有什么建议吗?
我原来的这个方法,既知道如何做底层的事情,也知道如何做高层的事情,这违反了单一抽象层次原则(SLAP)。在哪里可以找到更多关于 Go 语言中 SLAP 的详细信息(最好有示例或练习)?
更多关于Golang中的单一抽象层级原则(SLAP)示例详解的实战教程也可以访问 https://www.itying.com/category-94-b0.html
更多关于Golang中的单一抽象层级原则(SLAP)示例详解的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html
在Go语言中,单一抽象层级原则(SLAP)强调一个函数或方法应该在同一抽象层级上操作。你描述的方法确实违反了SLAP,因为它混合了文件I/O、数据解析、数据结构构建和数据库操作等多个不同层级的任务。以下是重构示例,展示如何分层组织代码:
1. 定义领域模型
type DataEntry struct {
Numbers []int
Words []string
}
type ComplexStructure struct {
Entries []DataEntry
}
2. 文件读取层
type FileReader interface {
ReadLines(filename string) ([]string, error)
}
type FileSystemReader struct{}
func (r *FileSystemReader) ReadLines(filename string) ([]string, error) {
content, err := os.ReadFile(filename)
if err != nil {
return nil, err
}
return strings.Split(string(content), "\n"), nil
}
3. 数据解析层
type LineParser interface {
ParseLine(line string) (*DataEntry, error)
}
type RegexParser struct {
numberRegex *regexp.Regexp
wordRegex *regexp.Regexp
}
func NewRegexParser() *RegexParser {
return &RegexParser{
numberRegex: regexp.MustCompile(`\d+`),
wordRegex: regexp.MustCompile(`[a-zA-Z]+`),
}
}
func (p *RegexParser) ParseLine(line string) (*DataEntry, error) {
numbers := p.numberRegex.FindAllString(line, -1)
words := p.wordRegex.FindAllString(line, -1)
var ints []int
for _, n := range numbers {
if val, err := strconv.Atoi(n); err == nil {
ints = append(ints, val)
}
}
return &DataEntry{
Numbers: ints,
Words: words,
}, nil
}
4. 数据构建层
type DataBuilder interface {
BuildStructure(entries []DataEntry) *ComplexStructure
}
type DefaultBuilder struct{}
func (b *DefaultBuilder) BuildStructure(entries []DataEntry) *ComplexStructure {
return &ComplexStructure{
Entries: entries,
}
}
5. 数据库层
type Database interface {
Connect() error
SaveStructure(structure *ComplexStructure) error
Close() error
}
type SQLDatabase struct {
conn *sql.DB
}
func (db *SQLDatabase) Connect() error {
conn, err := sql.Open("mysql", "user:pass@/dbname")
if err != nil {
return err
}
db.conn = conn
return nil
}
func (db *SQLDatabase) SaveStructure(structure *ComplexStructure) error {
// 实现数据库保存逻辑
for _, entry := range structure.Entries {
_, err := db.conn.Exec("INSERT INTO entries VALUES (?, ?)",
entry.Numbers, entry.Words)
if err != nil {
return err
}
}
return nil
}
6. 控制器层(高层协调)
type Processor struct {
reader FileReader
parser LineParser
builder DataBuilder
database Database
}
func NewProcessor(reader FileReader, parser LineParser,
builder DataBuilder, database Database) *Processor {
return &Processor{
reader: reader,
parser: parser,
builder: builder,
database: database,
}
}
func (p *Processor) ProcessFile(filename string) (*ComplexStructure, error) {
// 读取文件
lines, err := p.reader.ReadLines(filename)
if err != nil {
return nil, fmt.Errorf("读取文件失败: %w", err)
}
// 解析每一行
var entries []DataEntry
for _, line := range lines {
if line == "" {
continue
}
entry, err := p.parser.ParseLine(line)
if err != nil {
return nil, fmt.Errorf("解析行失败: %w", err)
}
entries = append(entries, *entry)
}
// 构建数据结构
structure := p.builder.BuildStructure(entries)
// 连接数据库并保存
if err := p.database.Connect(); err != nil {
return nil, fmt.Errorf("数据库连接失败: %w", err)
}
defer p.database.Close()
if err := p.database.SaveStructure(structure); err != nil {
return nil, fmt.Errorf("保存数据失败: %w", err)
}
return structure, nil
}
7. 使用示例
func main() {
processor := NewProcessor(
&FileSystemReader{},
NewRegexParser(),
&DefaultBuilder{},
&SQLDatabase{},
)
structure, err := processor.ProcessFile("data.txt")
if err != nil {
log.Fatal(err)
}
fmt.Printf("处理完成,共 %d 条记录\n", len(structure.Entries))
}
这个重构示例展示了如何将原始方法分解为多个单一职责的组件,每个组件都在自己的抽象层级上操作:
- FileReader: 只负责文件读取
- LineParser: 只负责行解析
- DataBuilder: 只负责数据结构构建
- Database: 只负责数据库操作
- Processor: 作为协调者,只负责调用顺序和错误处理
每个层都通过接口定义契约,这使得代码可测试、可维护,并且符合SLAP原则。Processor方法现在只在高抽象层级操作,不知道底层实现细节。

