V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
The Go Programming Language
http://golang.org/
Go Playground
Go Projects
Revel Web Framework
dzdh
V2EX  ›  Go 编程语言

go 怎么实现 方法前置操作 类似 PHP 的__call

  •  
  •   dzdh · 2022-03-10 12:35:35 +08:00 · 2848 次点击
    这是一个创建于 1017 天前的主题,其中的信息可能已经有所发展或是发生改变。

    如 php:

    class a
    {
        public function __call($method, $args) {
            return call_user_func([new b(), $method], $args);
        }
    }
    
    class b {}
    

    不能是 xx.call('method',...) 最好还是 xx.method

    场景是 三方包 不想改,三方包里要 xxx(handler{}) 然后 handler 里有一些方法。想实现在三方包调用 handler.xx 的时候先经过一次自己的验证。

    否则 handler 可能就要写成

    func (h *handler) m1 {
        if h.xx == nil {
            // xx
        }
    }
    func (h *handler) m2 {
        if h.xx == nil {
            // xx
        }
    }
    func (h *handler) m3 {
        if h.xx == nil {
            // xx
        }
    }
    
    20 条回复    2022-03-12 20:04:55 +08:00
    superfatboy
        1
    superfatboy  
       2022-03-10 13:30:53 +08:00
    貌似 好像 iris 有一个这个功能,能在路由前置执行,不知道符合不符合你的要求
    mybyons
        2
    mybyons  
       2022-03-10 13:45:05 +08:00
    Wrapper / Adapter

    ```go
    func log(h http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r
    *http.Request) {
    log.Println("Before")
    h.ServeHTTP(w, r) // call original
    log.Println("After")
    })
    }

    ```
    http.Handle("/path", handleThing)
    -->
    http.Handle("/path", log(handleThing))
    mybyons
        3
    mybyons  
       2022-03-10 13:46:29 +08:00
    Wrapper / Adapter

    ```go
    func log(h http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
    log.Println("Before")
    h.ServeHTTP(w, r) // call original
    log.Println("After")
    })
    }
    ```

    http.Handle("/path", handleThing)
    -->
    http.Handle("/path", log(handleThing))
    wangritian
        4
    wangritian  
       2022-03-10 14:35:18 +08:00
    go 没有魔术方法,只能自己包一层
    james122333
        5
    james122333  
       2022-03-10 18:39:20 +08:00
    __call 如果没做其他什么直接 type embeding 就可以
    有的话只能用反射外包一层 并且方法必须要是 field 不能是 struct 内 method
    不然完全找不到 Address...
    james122333
        6
    james122333  
       2022-03-10 19:00:20 +08:00
    可以自己写个 lib 用来初始化 类似 new 或者依赖注入都可以 乍看之下风格是差不多的 type embeding 的状况这些函数 field 也会有
    james122333
        7
    james122333  
       2022-03-10 19:01:58 +08:00
    你要用对象写是会很痛苦的
    Goooooos
        8
    Goooooos  
       2022-03-10 19:32:22 +08:00 via Android
    这不符合 go 的设计思想
    dobelee
        9
    dobelee  
       2022-03-11 01:29:22 +08:00
    是要实现类似统一参数校验的效果吗?这种一般包多一层 middleware ,相反 php 的魔术方法才是奇葩实现(相比其他主流)。
    lwldcr
        10
    lwldcr  
       2022-03-11 07:42:06 +08:00 via iPhone
    Middleware 或者叫拦截器 差不多就这样 把方法作为参数
    sampeng
        11
    sampeng  
       2022-03-11 08:32:27 +08:00 via iPhone
    出门右拐用 java ,python ,php ,等灯
    MeetTheFuture
        12
    MeetTheFuture  
       2022-03-11 10:07:54 +08:00
    Middleware
    bugfan
        13
    bugfan  
       2022-03-11 10:35:21 +08:00
    自己包装一层中间件,可以放在主要逻辑前,也可以放在主要逻辑后 类似 https://github.com/bugfan/srv
    dzdh
        14
    dzdh  
    OP
       2022-03-11 15:18:40 +08:00
    @james122333

    就是重写一遍 struct 挨个实现其方法。然后再方法内挨个走我自己的方法再调他原有的方法?

    ```
    type o struct{}
    func (_o *o) m1() {
    }



    type my struct {
    o
    }
    func (_my *my) m1() {
    // before
    _my.o.m1() // 或者我在 my 里再写个统一的 wrapper ?
    // after
    }

    ```

    然后 ` xxx.handle(&my{}) ` ?

    就是麻烦点。。
    james122333
        15
    james122333  
       2022-03-11 18:01:36 +08:00
    @dzdh

    这只是 type embedding 如果要实现你要的风格 并且有其他动作 就得替换方法包一层 每个都要呼叫魔术方法

    另外反射讲的是写个 library 把下列类似代码串起来(反射再包一层的方法是 reflect.MakeFunc) 然后如何初始化 struct 内 func 自己想想 毕竟每次 new 一个出来还要指定哪个 func 很麻烦 所以才会有后面那个`bind` 主要是透过 reflect.MakeFunc 替换原来 func field 指定先做魔术方法再做原来的方法
    type Test struct {
    Call func(t *Test) `bind:"Call"`
    A func(t *Test) `bind:"A"`
    }

    var Call = func(t *Test) {
    fmt.Println("Call")
    }

    var A = func(t *Test) {
    fmt.Println("A")
    }
    james122333
        16
    james122333  
       2022-03-11 18:26:48 +08:00 via Android
    那个 A 函数只要透过 makefunc 把 call 函数并入一次就可以 比较麻烦的是怎么让一切自动完成
    bugfan
        17
    bugfan  
       2022-03-11 19:28:57 +08:00
    @dzdh #14 这种还不算是最优解,其实可以参考一些框架的设计都用了反射,例如:
    ```

    type magic interface {
    Call(*some.Class)
    }

    // 把你的 model 传进去
    func Register(m interface{}){
    handlers=append(handlers,m)
    }

    // 在合适的时机执行
    func bind(h interface{}){
    // 一些逻辑处理 handlers

    reflectVal := reflect.ValueOf(h)
    t := reflect.Indirect(reflectVal).Type()
    newObj := reflect.New(t)
    handler, ok := newObj.Interface().(magic)
    if ok {
    handler.Call(xxxx)
    }
    // 然后走你 register 进去的 model 的逻辑

    }

    // 类似 “https://github.com/bugfan/rest/blob/master/rest.go”,不喜轻喷啊😂

    ```
    dzdh
        18
    dzdh  
    OP
       2022-03-11 22:46:39 +08:00
    @bugfan

    关键是 三方包里 的方法 不是要传入它要求的类型么。这咋搞。是人家的包。不是自己项目里的。自己的项目去调用人家的包。
    kongkongyzt
        19
    kongkongyzt  
       2022-03-12 09:48:14 +08:00
    无...
    james122333
        20
    james122333  
       2022-03-12 20:04:55 +08:00 via Android
    @dzdh

    如果是传入的是 type interface 可以这样做
    不然其实只是三方包不希望你这样做
    go 本身挺有限制 往好处想其实就是一堆人说的规范
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   2717 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 23ms · UTC 06:05 · PVG 14:05 · LAX 22:05 · JFK 01:05
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.