duansaxf095988 2016-08-03 04:34
浏览 20
已采纳

我的Go可组合性方法是惯用的吗?

I created this in the playground: https://play.golang.org/p/Jj4UhA8Yn7

I'll paste the code below as well.

The question revolves around whether my approach on composability is something I should consider as viable, good Go code, or if I'm thinking about it incorrectly and should consider something more in line with idiomatic Go.

My goal is to use this pattern to create "logic" tiers that decorate the underlying layer with additional logic that the wrapped layer should not need to know about.

As a cursory example I might have these "layers"

  1. interface layer - a set of interfaces that define the "model"
  2. simple struct layer - just holds data from the database meets the interfaces above
  3. validation layer - wraps around an interface from the interface layer and validates incoming data against some validation rules before it then forwards the method calls to the wrapped layer below. It also meets the same interface as the layer it is wrapping.
  4. translation layer - same as number 3, translates any text being accessed by first calling the layer it is wrapping, then translates the text, and returns the translated text. it will meet the same interface of the object it wraps. Any methods unrelated to getting info will be forwarded to the underlying layer transparently.

I hope I've made myself somewhat clear and that the example code below will help illustrate it better than my words above.

The example code from the playgroud

package main

import (
    "errors"
    "fmt"
)

//An interface
type Weird interface {
    Name() string
    SetName(name string) error

    Age() int
    SetAge(age int) error
}

//Simple struct to hold data
type SimpleWeird struct {
    name string
    age  int
}

func (s *SimpleWeird) Name() string {
    return s.name
}

func (s *SimpleWeird) SetName(name string) error {
    s.name = name
    return nil
}

func (s *SimpleWeird) Age() int {
    return s.age
}

func (s *SimpleWeird) SetAge(age int) error {
    s.age = age
    return nil
}

//RegularWeird encapsulates some "business" logic within it's methods
//and would be considered normal logic flow
type RegularWeird struct {
    Weird
}

func (r *RegularWeird) SetName(name string) error {
    if len(name) > 5 {
        return errors.New("Regulars can't set a name longer than 5 characters long")
    }
    return r.Weird.SetName(name)
}

func (r *RegularWeird) SetAge(age int) error {
    if age > 80 {
        return errors.New("Regulars can't set an age above 80")
    }

    return r.Weird.SetAge(age)
}

//AdminWeird encapsulates some admin "business" logic within it's methods
//It would be considered admin logic flow/rules
type AdminWeird struct {
    Weird
}

//AdminWeirds don't have their own SetName. If they
//Wrap a SimpleWeird then any name size is allowed (desired behavior)
//If the wrap a regular weird then the regular weird's logic is enforced

func (a *AdminWeird) SetAge(age int) error {
    if age > 100 {
        return errors.New("Admins can't set an age above 100")
    }
    return nil
}

func NewAdminWeird() Weird {
    return &AdminWeird{Weird: &SimpleWeird{}}
}

func NewRegularWeird() Weird {
    return &RegularWeird{Weird: &SimpleWeird{}}
}

//This one doesn't make sense for this example but I wanted to show
//the composability aspect of this. I would be creating chainable
//interfaces that each handle different unrelated logic
func NewAdminRegularWeird() Weird {
    return &AdminWeird{Weird: NewRegularWeird()}
}

func checkErr(err error) {
    if err != nil {
        fmt.Println(err)
    }
}

func main() {
    var err error

    r := NewRegularWeird()
    a := NewAdminWeird()
    ar := NewAdminRegularWeird()

    fmt.Println("Regular output:")
    err = r.SetName("test")
    checkErr(err)

    err = r.SetAge(5)
    checkErr(err)

    err = r.SetName("something-longer")
    checkErr(err)

    err = r.SetAge(90)
    checkErr(err)

    fmt.Println("Admin output:")
    err = a.SetName("test")
    checkErr(err)

    err = a.SetAge(5)
    checkErr(err)

    err = a.SetName("something-longer")
    checkErr(err)

    err = a.SetAge(101)
    checkErr(err)

    fmt.Println("AdminRegular output:")
    err = ar.SetName("test")
    checkErr(err)

    err = ar.SetAge(5)
    checkErr(err)

    err = ar.SetName("something-longer")
    checkErr(err)

    err = ar.SetAge(90)
    checkErr(err)

}
  • 写回答

1条回答 默认 最新

  • douliang2087 2016-08-03 15:26
    关注

    I'm not sure about idiomatic, but I think you've effectively used interfaces, structures and constructor methods to achieve your goal. Here are a few things you might change if it suits you.

    1) Let the implementations deal with the setters.

    //An interface
    type Weird interface {
        Name() string
    
        Age() int
    }
    

    Implementers could choose to implement a SetName or SetAge, but they could just as well initialize with values or assign directly to the structure. For good or bad, the business rules about min/max age, etc. are not enforceable by the interface, so the interface methods seems like extra code to me.

    2) Create an Admin interface, though you haven't really defined any unique behavior of Admins in your example:

    type Admin interface {
        Weird
        SomeAdminMethod() string
    }
    

    3) Your use of the interface as the return value for all of the New* functions is a bit confusing to me. It seems like that would be more suited to a single NewWeird function like:

    func NewWeird(flavor string) (Weird, error) {
        switch flavor {
        case "regular":
            return &RegularWeird{Weird: &SimpleWeird{}}, nil
        case "admin":
            return &AdminWeird{Weird: &SimpleWeird{}}, nil
        case "regularadmin":
            return &RegularWeird{Weird: &NewWeird(regular)}, nil
        case default:
            return nil, errors.Error("unknown weird type")
        }
    }
    

    The use of the interface as the return is handy if you want to initialize a bunch of heterogeneous Weirds and stuff them into a []Weird or something like that.

    Hope it helps...

    本回答被题主选为最佳回答 , 对您是否有帮助呢?
    评论

报告相同问题?

悬赏问题

  • ¥100 任意维数的K均值聚类
  • ¥15 stamps做sbas-insar,时序沉降图怎么画
  • ¥15 unity第一人称射击小游戏,有demo,在原脚本的基础上进行修改以达到要求
  • ¥15 买了个传感器,根据商家发的代码和步骤使用但是代码报错了不会改,有没有人可以看看
  • ¥15 关于#Java#的问题,如何解决?
  • ¥15 加热介质是液体,换热器壳侧导热系数和总的导热系数怎么算
  • ¥100 嵌入式系统基于PIC16F882和热敏电阻的数字温度计
  • ¥15 cmd cl 0x000007b
  • ¥20 BAPI_PR_CHANGE how to add account assignment information for service line
  • ¥500 火焰左右视图、视差(基于双目相机)