详解Go语言如何利用高阶函数写出优雅的代码

目录
  • 前言
  • 问题
  • 白银
  • 黄金
  • 王者
  • 总结

前言

go项目中经常需要查询db,按照以前java开发经验,会根据查询条件写很多方法,如:

  • GetUserByUserID
  • GetUsersByName
  • GetUsersByAge

每一种查询条件写一个方法,这种方式对外是挺好的,对外遵循严格原则,让每个对外的方法接口是明确的。但是对内的话,应该尽可能的通用,做到代码复用,少写代码,让代码看起来更优雅、整洁。

问题

在review代码的时候,针对上面3个方法,一般写法是

func GetUserByUserID(ctx context.Context, userID int64) (*User, error){
    db := GetDB(ctx)
    var user User
    if userID > 0 {
        db = db.Where(`userID = ?`, userID)
    }
    if err := db.Model(&User{}).Find(&user).Err; err != nil {
        return nil, err
    }

    return user, nil
}

func GetUsersByName(ctx context.Context, name string) (*User, error){
    db := GetDB(ctx)
    var users []User
    if name != "" {
        db = db.Where(`name like '%%'`, name)
    }
    if err := db.Model(&User{}).Find(&users).Err; err != nil {
        return nil, err
    }

    return users, nil
}

func GetUsersByAge(ctx context.Context, age int64) (*User, error){
    db := GetDB(ctx)
    var user User
    if age > 0 {
        db = db.Where(`age = ?`, age)
    }
    if err := db.Model(&User{}).Find(&user).Err; err != nil {
        return nil, err
    }

    return user, nil
}

当User表上字段有几十个的时候,上面类似的方法会越来越多,代码没有做到复用。当有Teacher表、Class表等其他表的时候,上面的查询方法又要翻倍。

调用方也会写的很死,参数固定。当要增加一个查询条件的时候,要么改原来的函数,增加一个参数,这样其他调用的地方也都要改;要么新写一个函数,这样函数越来越多,难以维护和阅读。

上面是青铜写法,针对这种情况,下面介绍几种白银、黄金、王者写法

白银

将入参定义成一个结构体

type UserParam struct {
    ID int64
    Name string
    Age int64
}

将入参都放在UserParam结构体中

func GetUserInfo(ctx context.Context, info *UserParam) ([]*User, error) {
        db := GetDB(ctx)
        db = db.Model(&User{})
        var infos []*User
        if info.ID > 0 {
            db = db.Where("user_id = ?", info.ID)
        }
        if info.Name != "" {
            db = db.Where("user_name = ?", info.Name)
        }
        if info.Age > 0 {
            db = db.Where("age = ?", info.Age)
        }
        if err := db.Find(&infos).Err; err != nil {
            return nil, err
        }

        return infos, nil
}

这个代码写到这里,相比最开始的方法其实已经好了不少,至少 dao 层的方法从很多个入参变成了一个,调用方的代码也可以根据自己的需要构建参数,不需要很多空占位符。但是存在的问题也比较明显:仍然有很多判空不说,还引入了一个多余的结构体。如果我们就到此结束的话,多少有点遗憾。

另外,如果我们再扩展一下业务场景,我们使用的不是等值查询,而是多值查询或者区间查询,比如查询 status in (a, b),那上面的代码又怎么扩展呢?是不是又要引入一个方法,方法繁琐暂且不说,方法名叫啥都会让我们纠结很久;或许可以尝试把每个参数都从单值扩展成数组,然后赋值的地方从 = 改为 in()的方式,所有参数查询都使用 in 显然对性能不是那么友好。

黄金

更高级的优化方法,是使用高阶函数。

type Option func(*gorm.DB)

定义 Option 是一个函数,这个函数的入参类型是*gorm.DB,返回值为空。

然后针对每一个需要查询的字段,定义一个高阶函数

func UserID(ID int64) Option {
    return func(db *gorm.DB) {
        db.Where("`id` = ?", ID)
    }
}

func Name(name int64) Option {
    return func(db *gorm.DB) {
        db.Where("`name` like %?%", name)
    }
}

func Age(age int64) Option {
    return func(db *gorm.DB) {
        db.Where("`age` = ?", age)
    }
}

返回值是Option类型。

这样上面3个方法就可以合并成一个方法了

func GetUsersByCondition(ctx context.Context, opts ...Option)([]*User, error) {
    db := GetDB(ctx)
    for i:=range opts {
        opts[i](db)
    }
    var users []User
    if err := db.Model(&User{}).Find(&users).Err; err != nil {
        return nil, err
    }
    return users, nil
}

没有对比就没有伤害,通过和最开始的方法比较,可以看到方法的入参由多个不同类型的参数变成了一组相同类型的函数,因此在处理这些参数的时候,也无需一个一个的判空,而是直接使用一个 for 循环就搞定,相比之前已经简洁了很多。

还可以扩展其他查询条件,比如IN,大于等

func UserIDs(IDs int64) Option {
    return func(db *gorm.DB) {
        db.Where("`id` in (?)", IDs)
    }
}

func AgeGT(age int64) Option {
    return func(db *gorm.DB) {
        db.Where("`age` > ?", age)
    }
}

而且这个查询条件最终是转换成Where条件,跟具体的表无关,也就是说这些定义是可以被其他表复用的。

王者

优化到上述方法已经可以了,但是王者一般会继续优化。

上述方法GetUsersByCondition只能查User表,能不能更通用一些,查任意表呢?分享GetUsersByCondition方法,发现如果要做到查任意表,有2个阻碍:

  • 表明是在方法中写死的
  • 返回值定义的是[]*User,不能通用

针对第一个问题,我们可以定义一个Option来实现

func TableName(tableName string) Option {
    return func(db *grom.DB) {
        db.Table(tableName)
    }
}

针对第二个问题,可以将返回参数作为入参,通过引用的方式传进来

func GetRecords(ctx context.Context, in any, opts ...Option) {
    db := GetDB(ctx)
    for i:=range opts {
        opts[i](db)
    }

    return db.Find(in).Err
}

// 调用:根据user name 和age 查询users
var users []User
if err := GetRecords(ctx, &users, TableName("user"), Name("张三"), Age(18)); err != nil {
    // TODO
}

总结

这里通过对 grom 查询条件的抽象,大大简化了对 DB 组合查询的写法,提升了代码的简洁。

以上就是详解Go语言如何利用高阶函数写出优雅的代码的详细内容,更多关于Go语言高阶函数的资料请关注我们其它相关文章!

(0)

相关推荐

  • 一文带你熟悉Go语言中函数的使用

    目录 函数 函数的声明 Go 函数支持变长参数 匿名函数 闭包 init 函数 函数参数详解 形式参数与实际参数 值传递 函数是一种数据类型 小结 函数 函数的英文单词是 Function,这个单词还有着功能的意思.在 Go 语言中,函数是实现某一特定功能的代码块.函数代表着某个功能,可以在同一个地方多次使用,也可以在不同地方使用.因此使用函数,可以提高代码的复用性,减少代码的冗余. 函数的声明 通过案例了解函数的声明有哪几部分: 定义一个函数,实现两个数相加的功能,并将相加之后的结果返回. f

  • Go语言入门之函数的定义与使用

    目录 1.前言 2.函数声明 2.1 函数例子 2.2 Go 函数支持多返回值 2.3 变量函数 2.4 闭包 2.5 递归 3.总结 1.前言 函数是一段代码的片段,包含连续的执行语句,它可以将零个或多个输入参数映射到零个或多个参数输出.函数像一个黑盒,对它的使用者隐藏实现细节.还可以在代码中通过函数调用来执行它们. 学到现在,我们使用的 Go 函数只有 main 函数: func main() { } 2.函数声明 每个函数都包含 func 关键字.函数名.输入参数列表.一个可选的返回列表以

  • 详解Go语言如何利用高阶函数写出优雅的代码

    目录 前言 问题 白银 黄金 王者 总结 前言 go项目中经常需要查询db,按照以前java开发经验,会根据查询条件写很多方法,如: GetUserByUserID GetUsersByName GetUsersByAge 每一种查询条件写一个方法,这种方式对外是挺好的,对外遵循严格原则,让每个对外的方法接口是明确的.但是对内的话,应该尽可能的通用,做到代码复用,少写代码,让代码看起来更优雅.整洁. 问题 在review代码的时候,针对上面3个方法,一般写法是 func GetUserByUse

  • 详解python内置常用高阶函数(列出了5个常用的)

    高阶函数是在Python中一个非常有用的功能函数,所谓高阶函数就是一个函数可以用来接收另一个函数作为参数,这样的函数叫做高阶函数. python内置常用高阶函数: 一.函数式编程 •函数本身可以赋值给变量,赋值后变量为函数: •允许将函数本身作为参数传入另一个函数: •允许返回一个函数. 1.map()函数 是 Python 内置的高阶函数,它接收一个函数 f 和一个 list, 并通过把函数 f 依次作用在 list 的每个元素上,得到一个新的 list 并返回 def add(x): ret

  • 详解Angular路由动画及高阶动画函数

    一.路由动画 路由动画需要在host元数据中指定触发器.动画注意不要过多,否则适得其反. 内容优先,引导用户去注意到某个内容.动画只是辅助手段. 在router.animate.ts中定义一个进场动画,一个离场动画. 因为进场动画和离场动画用的特别频繁,有一个别名叫:enter和:leave. import { trigger, state, transition, style, animate} from '@angular/animations'; export const slideToR

  • python利用高阶函数实现剪枝函数

    本文为大家分享了python利用高阶函数实现剪枝函数的具体代码,供大家参考,具体内容如下 案例: 某些时候,我们想要为多个函数,添加某种功能,比如计时统计,记录日志,缓存运算结果等等 需求: 在每个函数中不需要添加完全相同的代码 如何解决? 把相同的代码抽调出来,定义成装饰器 求斐波那契数列(黄金分割数列),从数列的第3项开始,每一项都等于前两项之和 求一个共有10个台阶的楼梯,从下走到上面,一次只能迈出1~3个台阶,并且不能后退,有多少中方法? 上台阶问题逻辑整理: 每次迈出都是 1~3 个台

  • 详解PyTorch中Tensor的高阶操作

    条件选取:torch.where(condition, x, y) → Tensor 返回从 x 或 y 中选择元素的张量,取决于 condition 操作定义: 举个例子: >>> import torch >>> c = randn(2, 3) >>> c tensor([[ 0.0309, -1.5993, 0.1986], [-0.0699, -2.7813, -1.1828]]) >>> a = torch.ones(2,

  • 详解C语言结构体中的函数指针

    结构体是由一系列具有相同类型或不同类型的数据构成的数据集合.所以,标准C中的结构体是不允许包含成员函数的,当然C++中的结构体对此进行了扩展.那么,我们在C语言的结构体中,只能通过定义函数指针的方式,用函数指针指向相应函数,以此达到调用函数的目的. 函数指针 函数类型 (*指针变量名)(形参列表):第一个括号一定不能少. "函数类型"说明函数的返回类型,由于"()"的优先级高于"*",所以指针变量名外的括号必不可少.  注意指针函数与函数指针表示

  • Swift中的高阶函数功能作用示例详解

    目录 高阶函数的作用 1. 简化代码 2. 提高可读性 3. 支持函数式编程 4. 提高代码的可重用性 常见的高阶函数 1. map() 2. filter() 3. reduce() 4. sorted() 5. forEach() 6. compactMap() 7. flatMap() 8. zip() 9. first() 10. contains() 高阶函数的作用 Swift中的高阶函数是指那些参数或返回值是函数的函数.它们的存在使得我们可以用非常简洁和优雅的代码来解决许多问题. 1

  • Javascript 是你的高阶函数(高级应用)

    在通常的编程语言中,函数的参数只能是基本类型或者对象引用,返回值也只是基本数据类型或对象引用.但在Javascript中函数作为一等公民,既可以当做参数传递,也可以被当做返回值返回.所谓高阶函数就是可以把函数作为参数,或者是将函数作为返回值的函数.这两种情形在实际开发中有很多应用场景,本文是我在工作学习中遇到的几种应用场景的总结. 回调函数 代码复用是衡量一个应用程序的重要标准之一.通过将变化的业务逻辑抽离封装在回调函数中能够有效的提高代码复用率.比如ES5中为数组增加的forEach方法,遍历

  • 玩转Kotlin 彻底弄懂Lambda和高阶函数

    Lambda是什么 简单来讲,Lambda是一种函数的表示方式(言外之意也就是说一个Lambda表达式等于一个函数).更确切的说:Lambda是一个未声明的函数,会以表达式的形式传递 为什么要用Lambda 设想一下,在Android中实现一个View的点击事件,可以使用如下实现: View view = findViewById(R.id.textView); view.setOnClickListener(new View.OnClickListener() { @Override publ

  • JS高阶函数原理与用法实例分析

    本文实例讲述了JS高阶函数原理与用法.分享给大家供大家参考,具体如下: 如果您正在学习JavaScript,那么您必须遇到高阶函数这个术语.这听起来复杂,其实不然. 使JavaScript适合函数式编程的原因是它接受高阶函数. 高阶函数在JavaScript中广泛使用.如果你已经用JavaScript编程了一段时间,你可能已经使用它们甚至不知道. 要完全理解这个概念,首先必须了解函数式编程是什么一等函数(first-Class Function)以及的概念. 函数式编程 在大多数简单的术语中,函

随机推荐