Golang中的单一抽象层级原则(SLAP)示例详解

Golang中的单一抽象层级原则(SLAP)示例详解 大家好,

我有一个方法,它:

  • 打开一个文件 F
  • 读取其内容
  • 从每一行 L 中解析出一些有用的数字和单词
  • 创建一个复杂的数据结构 S
  • 将每个解析出的数据 L 添加到 S 中
  • 创建一个数据库实例
  • 将复杂的数据结构写入数据库
  • 返回数据结构和一个数据库连接

在我看来,这个方法违反了单一职责原则,因为它一次做了太多事情。应该重构它,使其成为一个控制器方法:它本身不知道如何做任何这些事情,而只是调用其他更小的、分别完成这些任务的方法。

因此,我打算重构它,让它只负责知道要调用哪些其他方法、正确的调用顺序以及进行错误处理。

但这个方法(原始形式,而非重构后的)还有更多我不喜欢的地方:一个构建数据结构的方法不应该知道如何解析文件的每一行,或者如何创建特定的数据库。解析一行文本(字符串)应该在另一层完成,读写数据库应该在另一层完成,等等。

我应该如何安排这种分层结构?对于如何将我的代码所调用的所有这些小方法组织成不同的层,有什么建议吗?

我原来的这个方法,既知道如何做底层的事情,也知道如何做高层的事情,这违反了单一抽象层次原则(SLAP)。在哪里可以找到更多关于 Go 语言中 SLAP 的详细信息(最好有示例或练习)?


更多关于Golang中的单一抽象层级原则(SLAP)示例详解的实战教程也可以访问 https://www.itying.com/category-94-b0.html

1 回复

更多关于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方法现在只在高抽象层级操作,不知道底层实现细节。

回到顶部