我的可组合性方法是 Go 惯用的吗?
Is my approach to composability idiomatic for Go?
我在操场上创建了这个:https://play.golang.org/p/Jj4UhA8Yn7
我也会把代码贴在下面。
问题围绕着我的可组合性方法是否是我应该考虑的可行的、好的 Go 代码,或者我是否错误地考虑它并且应该考虑更符合惯用的 Go 的东西。
我的目标是使用此模式创建 "logic" 层,用包装层不需要知道的额外逻辑来装饰底层。
作为一个粗略的例子,我可能有这些 "layers"
- 接口层 - 定义 "model"
的一组接口
- 简单的结构层 - 只保存来自数据库的数据满足上面的接口
- 验证层 - 包装来自接口层的接口并根据一些验证规则验证传入数据,然后将方法调用转发到下面的包装层。它还满足与它所包裹的层相同的界面。
- 翻译层 - 与第 3 层相同,通过首先调用它正在包装的层来翻译正在访问的任何文本,然后翻译文本,returns 翻译后的文本。它将遇到它包装的对象的相同接口。任何与获取信息无关的方法都会透明转发到底层
我希望我已经讲清楚了,下面的示例代码将比我上面的话更好地说明它。
来自 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) 让实现处理设置器。
//An interface
type Weird interface {
Name() string
Age() int
}
实施者可以选择实施 SetName 或 SetAge,但他们也可以使用值进行初始化或直接分配给结构。不管是好是坏,有关 min/max 年龄等的业务规则不能由接口强制执行,因此接口方法对我来说似乎是额外的代码。
2) 创建一个 Admin 界面,尽管您还没有在示例中真正定义 Admins 的任何独特行为:
type Admin interface {
Weird
SomeAdminMethod() string
}
3) 您将接口用作所有 New* 函数的 return 值让我有点困惑。这似乎更适合单个 NewWeird 函数,例如:
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")
}
}
如果你想初始化一堆异构的 Weirds 并将它们塞进 []Weird
或类似的东西,使用接口作为 return 是很方便的。
希望对您有所帮助...
我在操场上创建了这个:https://play.golang.org/p/Jj4UhA8Yn7
我也会把代码贴在下面。
问题围绕着我的可组合性方法是否是我应该考虑的可行的、好的 Go 代码,或者我是否错误地考虑它并且应该考虑更符合惯用的 Go 的东西。
我的目标是使用此模式创建 "logic" 层,用包装层不需要知道的额外逻辑来装饰底层。
作为一个粗略的例子,我可能有这些 "layers"
- 接口层 - 定义 "model" 的一组接口
- 简单的结构层 - 只保存来自数据库的数据满足上面的接口
- 验证层 - 包装来自接口层的接口并根据一些验证规则验证传入数据,然后将方法调用转发到下面的包装层。它还满足与它所包裹的层相同的界面。
- 翻译层 - 与第 3 层相同,通过首先调用它正在包装的层来翻译正在访问的任何文本,然后翻译文本,returns 翻译后的文本。它将遇到它包装的对象的相同接口。任何与获取信息无关的方法都会透明转发到底层
我希望我已经讲清楚了,下面的示例代码将比我上面的话更好地说明它。
来自 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) 让实现处理设置器。
//An interface
type Weird interface {
Name() string
Age() int
}
实施者可以选择实施 SetName 或 SetAge,但他们也可以使用值进行初始化或直接分配给结构。不管是好是坏,有关 min/max 年龄等的业务规则不能由接口强制执行,因此接口方法对我来说似乎是额外的代码。
2) 创建一个 Admin 界面,尽管您还没有在示例中真正定义 Admins 的任何独特行为:
type Admin interface {
Weird
SomeAdminMethod() string
}
3) 您将接口用作所有 New* 函数的 return 值让我有点困惑。这似乎更适合单个 NewWeird 函数,例如:
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")
}
}
如果你想初始化一堆异构的 Weirds 并将它们塞进 []Weird
或类似的东西,使用接口作为 return 是很方便的。
希望对您有所帮助...