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...

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

报告相同问题?

悬赏问题

  • ¥15 (标签-STM32|关键词-智能小车)
  • ¥20 关于#stm32#的问题,请各位专家解答!
  • ¥15 (标签-python)
  • ¥15 第一个已完成,求第二个做法
  • ¥20 搭建awx,试了很多版本都有错
  • ¥15 java corba的客户端该如何指定使用本地某个固定IP去连接服务端?
  • ¥15 activiti工作流问题,求解答
  • ¥15 有人写过RPA后台管理系统么?
  • ¥15 Bioage计算生物学年龄
  • ¥20 如何将FPGA Alveo U50恢复原来出厂设置哇?