go语言使用组合降低耦合度

之前在用go语言开发考试系统,开发的时候没有想那么多,在处理题目相关的逻辑时都是在各个api接口中使用switch-case实现。这样实现起来确实简单快捷,修改起来也比较方便。但是随着业务越来越多,在不同api中处理不同题型的业务逻辑也越来越多。直到有了新的需求,增加两个新题型的时候,我才意识到这样做不行。

因为新增题型之后要改的代码文件太多了,再加上很长时间没有修改代码,很容易就遗漏了某个api接口中也需要对新题型进行处理。有一个比较笨的方法是,在文档中列出新增题型需要处理的api接口,然后挨个添加处理逻辑。不过这个方法治标不治本,还是决定用面向对象的方式来实现。

// 之前的处理逻辑
func UpdateQuestion(req *UpdateQuestionRequest) error {
    // ...
    switch req.QuestionType {
    case "single_choice":
    case "multiple_choice":
    case "short_answer":
    }
    // ...
}

当然我们都知道go语言中面向对象的特性比较少,自然会想到用接口来实现。所以我一开始的想法是:

// 定义接口和方法
type Question interface {
    UpdateQuestion(req *UpdateQuestionRequest) error
}

type SingleChoice struct {}

func (s *SingleChoice) UpdateQuestion(req *UpdateQuestionRequest) error {
    // ...
    return nil
}

这样的实现方式优点很明显,新增题型时只需要定义一个新的题目类型结构体,然后添加Question接口中定义的每一个方法即可。但是也有一定的局限性,就是Question接口中有很多方法时,需要对每一个方法都进行实现。而且有些方法可能逻辑上有一定的重叠,可能需要复制很多代码,这样有点麻烦。

于是我想到了之前听说的一种概念:组合。也许正好适用于这个业务场景:

type Question interface {
    UpdateQuestion(req *UpdateQuestionRequest) error
}

type baseQuestion struct {}

func (s *baseQuestion) UpdateQuestion(req *UpdateQuestionRequest) error {
    // ...
    return nil
}

type SingleChoice struct {
    baseQuestion
}

我相信看了这段代码,即使不知道组合概念的朋友也能很快理解。组合就是可以允许一个结构体包含另一个结构体,此时不仅继承了这个结构体的字段,还可以调用这个结构体的方法。像SingleChoice这种结构体看似没有实现Question接口的方法,但因为包含了baseQuestion结构体,所以也实现了Question接口,并且可以调用baseQuestion结构体的方法。

使用组合的好处:

  • 代码复用:避免重复编写相同逻辑的代码
  • 灵活性:使用组合可以灵活地组合多个不同的功能模块,满足不同场景下的业务需求
  • 扩展性:可以通过组合增加新的功能或者修改现有的功能,不会影响其他部分代码
// 如果不满足baseQuestion结构体中的方法,可以单独实现,并且可以调用baseQuestion结构体的方法
func (s *SingleChoice) UpdateQuestion(req *UpdateQuestionRequest) error {
    s.baseQuestion.UpdateQuestion(req)
    // ...
}

当然在我们具体的业务中,Question接口有非常多的方法,差不多花了一下午时间把原来switch-case的逻辑都在成员方法中实现了。有些题型可能公用相同的逻辑,就只实现baseQuestion结构体中的方法即可,其他题型再实现自己的方法。还可以用baseQuestion实现公共逻辑,把每个题型都存在的逻辑抽象出来。

此时还需要一个创建Question接口的工厂方法,用于创建不同的题型的实例。

func NewQuestion(questionType string) (Question, error) {
    switch questionType {
    case "single_choice":
        return &SingleChoice{}, nil
    case "multiple_choice":
        return &MultipleChoice{}, nil
    case "short_answer":
        return &ShortAnswer{}, nil
    }
    return nil, errors.New("invalid question type")
}

之前使用switch-case的api接口也需要修改调用方式:

func UpdateQuestion(req *UpdateQuestionRequest) error {
    question, err := NewQuestion(req.QuestionType)
    if err != nil {
        return err
    }
    return question.UpdateQuestion(req)
}

到此我们的优化就基本完成了,当然这些代码都是演示用的并非真实业务场景。以后再新增题型,只需要定义一个新的题型结构体,包含baseQuestion结构体,然后实现接口中的方法,最后加到工厂方法中即可。

用这种方式重构了不同题型的业务逻辑,提高了工程代码的可扩展性和可维护性,降低了耦合度。而且也为自己后续的业务扩展提供了方便,还提高了自身对于代码设计模式的理解,赢麻了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值