剛開始學習泛型。我正在制作一個命令處理器,老實說,我不知道如何表達,所以我將展示一個示例問題:
var ErrInvalidCommand = errors.New("invalid command")
type TransactionalFn[T any] func(ctx context.Context, db T) error
func NewTransactionalCommand[T any](fn TransactionalFn[T]) *TransactionalCommand[T] {
return &TransactionalCommand[T]{
fn: fn,
}
}
type TransactionalCommand[T any] struct {
fn TransactionalFn[T]
}
func (cmd *TransactionalCommand[T]) StartTransaction() error {
return nil
}
func (cmd *TransactionalCommand[T]) Commit() error {
return nil
}
func (cmd *TransactionalCommand[T]) Rollback() error {
return nil
}
type CMD interface{}
type CommandManager struct{}
func (m *CommandManager) Handle(ctx context.Context, cmd CMD) error {
switch t := cmd.(type) {
case *TransactionalCommand[any]:
return m.handleTransactionalCommand(ctx, t)
default:
fmt.Printf("%T\n", cmd)
return ErrInvalidCommand
}
}
func (m *CommandManager) handleTransactionalCommand(ctx context.Context, cmd *TransactionalCommand[any]) error {
if err := cmd.StartTransaction(); err != nil {
return err
}
if err := cmd.fn(ctx, nil); err != nil {
if err := cmd.Rollback(); err != nil {
return err
}
}
if err := cmd.Commit(); err != nil {
return err
}
return nil
}
// tests
type db struct{}
func (*db) Do() {
fmt.Println("doing stuff")
}
func TestCMD(t *testing.T) {
ctx := context.Background()
fn := func(ctx context.Context, db *db) error {
fmt.Println("test cmd")
db.Do()
return nil
}
tFn := bus.NewTransactionalCommand(fn)
mng := &bus.CommandManager{}
err := mng.Handle(ctx, tFn)
if err != nil {
t.Fatal(err)
}
}
mng.handle
回傳ErrInvalidCommand
,因此測驗失敗,因為cmd
is*TransactionalCommand[*db]
和 not*TransactionalCommand[any]
讓我再舉一個更抽象的例子:
type A[T any] struct{}
func (*A[T]) DoA() { fmt.Println("do A") }
type B[T any] struct{}
func (*B[T]) DoB() { fmt.Println("do B") }
func Handle(s interface{}) {
switch x := s.(type) {
case *A[any]:
x.DoA()
case *B[any]:
x.DoB()
default:
fmt.Printf("%T\n", s)
}
}
func TestFuncSwitch(t *testing.T) {
i := &A[int]{}
Handle(i) // expected to print "do A"
}
為什么這個 switch 陳述句大小寫不*A[any]
匹配*A[int]
?如何CommandManager.Handle(...)
接受通用命令?
uj5u.com熱心網友回復:
*A[any]
不匹配*A[int]
,因為any
是靜態型別,而不是通配符。因此,實體化具有不同型別的通用結構會產生不同的型別。
為了正確匹配型別開關中的泛型結構,您必須使用型別引數對其進行實體化:
func Handle[T any](s interface{}) {
switch x := any(s).(type) {
case *A[T]:
x.DoA()
case *B[T]:
x.DoB()
default:
panic("no match")
}
}
盡管 infer 沒有其他函式引數T
,但您必須Handle
使用顯式實體化進行呼叫。T
不會僅從結構中推斷出來。
func main() {
i := &A[int]{}
Handle[int](i) // expected to print "do A"
}
游樂場:https ://go.dev/play/p/2e5E9LSWPmk
但是 whenHandle
實際上是一個方法,就像在您的資料庫代碼中一樣,這具有在實體化接收器時選擇型別引數的缺點。
為了改進這里的代碼,您可以創建Handle
一個頂級函式:
func Handle[T any](ctx context.Context, cmd CMD) error {
switch t := cmd.(type) {
case *TransactionalCommand[T]:
return handleTransactionalCommand(ctx, t)
default:
fmt.Printf("%T\n", cmd)
return ErrInvalidCommand
}
}
然后你就有了如何為db T
命令函式提供引數的問題。為此,您可以:
只需將附加
*db
引數傳遞給Handle
andhandleTransactionalCommand
,這也有助于型別引數推斷。呼叫為Handle(ctx, &db{}, tFn)
. 游樂場:https ://go.dev/play/p/6WESb86KN5D傳遞一個實體
CommandManager
(如上面的解決方案,但*db
已包裝)。更加冗長,因為它需要在任何地方進行顯式實體化。游樂場:https ://go.dev/play/p/SpXczsUM5aW改用引數化介面(如下所示)。因此,您甚至不必進行型別切換。游樂場:https ://go.dev/play/p/EgULEIL6AV5
type CMD[T any] interface {
Exec(ctx context.Context, db T) error
}
轉載請註明出處,本文鏈接:https://www.uj5u.com/yidong/470429.html
下一篇:傳遞給泛型時合并兩個介面的鍵