切片和接口操作

huangapple go评论72阅读模式
英文:

Slice and interface manipulation

问题

我最近开始在Google App Engine上使用Go进行编程,遇到了一个难题。我来自Java领域,所以适应Go有点困难。

我想要一个方法,允许我传入一个指向切片的指针,然后将其传递给datastore.GetAll调用以检索结果。然后,我想要遍历结果并使用断言将其转换为特定接口(Queryable)以调用方法Map()。

最初,我有这个功能正常运行:

func (s ProjectService) RunQuery(context context.Context, q *datastore.Query, projects *[]Project) error {
    keys, err := q.GetAll(context, projects)
    if err != nil {
        return err
    }

    for i, key := range keys {
        (*projects)[i].Id = key.Encode()
        (*projects)[i].CompanyId = (*projects)[i].Company.Encode()
    }
    return nil
}

我想要一个更通用的方法,可以应用于任何实现Queryable接口的实体。我的想法是在检索结果后执行一些后处理的钩子。我已经研究了ProperyLoadSaver接口,但是我无法访问与实体关联的实际键。我想将datastore.Key的字符串表示存储在实体中。

这是Queryable接口:

type Queryable interface {
    Map(*datastore.Key) error
}

这是一个我正在持久化到GAE存储的示例实体:

type Camera struct {
    Id        string `datastore:"-"`
    ProjectId string `datastore:"-"`
    Name      string
    Project   *datastore.Key `json:"-"`
    Active    bool
    Timestamp Timestamp
}

// 实现Queryable接口。在这里可以执行任何其他映射操作
func (c *Camera) Map(key *datastore.Key) error {
    c.Name = "Maybe do other things here"
    c.Id = key.Encode()
    return nil
}

我的想法是像下面的代码片段一样实现:

func (c Crud) RunQuery(context context.Context, q *datastore.Query, entities interface{}) error {
    keys, err := q.GetAll(context, entities)    
    v := reflect.ValueOf(entities)
    dv := v.Elem()

    for i, key := range keys {        
        // 我保留了这个代码以显示它的工作方式,但这不会让我强制执行接口约定
        // dv.Index(i).FieldByName("Id").Set(reflect.ValueOf(key.Encode())) 

        entity := dv.Index(i).Interface().(Queryable)
        entity.Map(key)    
    }

    return err
}

然而,当执行这段代码时,会出现以下错误:

PANIC: interface conversion: entity.Camera is not entity.Queryable: missing method Map goroutine 9 [running]:

作为一个注意,我意识到执行断言的适当方式是使用if as, ok := elem.(Type); ok {},但我只是想看看错误是什么。

我猜测我之所以会得到这个错误,是因为我用指针接收器定义了我的参数 func (c *Camera) Map(key *datastore.Key) error 而不是 func (c Camera) Map(key *datastore.Key) error。然而,我想要修改实际的值。

我在哪里出错了?是因为我太过于Java风格了吗?

由于我对Go非常陌生,我可能完全错误地处理了这个问题。

英文:

I have recently started programming with Go on Google App Engine and I have run into a road block. I come from Java land so it's been a slight struggle to adapt to Go.

I want to have a method that allows me to pass in a pointer to a slice that I can then pass into the datastore.GetAll call to retrieve the results. I then want to iterate through the results and use an assertion to cast as a specific interface (Queryable) in order to call a method Map().

Initially, I had this functioning properly:

func (s ProjectService) RunQuery(context context.Context, q *datastore.Query, projects *[]Project) error {
	keys, err := q.GetAll(context, projects)
	if err != nil {
		return err
	}

	for i, key := range keys {
		(*projects)[i].Id = key.Encode()
		(*projects)[i].CompanyId = (*projects)[i].Company.Encode()
	}
	return nil
}

I want to have a more generic method that can be applied to any entity that implements a Queryable interface. The idea is to have a hook that allows me to perform some post processing after retrieving the results. I've looked into the ProperyLoadSaver interface however I have no access to the actual key that is associated to the entity. I would like to store the string representation of the datastore.Key in the entity.

This is the Queryable interface:

type Queryable interface {
	Map(*datastore.Key) error
}

Here's an example entity that I am persisting to the GAE store:

type Camera struct {
	Id        string `datastore:"-"`
	ProjectId string `datastore:"-"`
	Name      string
	Project   *datastore.Key `json:"-"`
	Active    bool
	Timestamp Timestamp
}

// Implement Queryable interface. Let me perform any additional mapping
func (c *Camera) Map(key *datastore.Key) error {
	c.Name = "Maybe do other things here"
	c.Id = key.Encode()
	return nil
}

The idea is to have something like the snippet below.

func (c Crud) RunQuery(context context.Context, q *datastore.Query, entities interface{}) error {
	keys, err := q.GetAll(context, entities)    
	v := reflect.ValueOf(entities)
	dv := v.Elem()

	for i, key := range keys {    		
// I left this in to show that this worked however this won't let me enforce the interface contract
//dv.Index(i).FieldByName("Id").Set(reflect.ValueOf(key.Encode())) 

		entity := dv.Index(i).Interface().(Queryable)
		entity.Map(key)    
	}

	return err
}

However, when this executes, it panics with the following:

PANIC: interface conversion: entity.Camera is not entity.Queryable: missing method Map goroutine 9 [running]:

Just as a note, I realize the appropriate way to perform an assertion is to do if as, ok := elem.(Type); ok {} but I just wanted to see what the error was

I am guessing I am getting this error because I have defined my parameter with a pointer receiver func (c *Camera) Map(key *datastore.Key) error and not func (c Camera) Map(key *datastore.Key) error However, I want to modify the actual value.

Where am I going wrong with this? Is my Java-ness showing?

Being that I am very new to Go, I may be approaching this completely wrong.

答案1

得分: 1

因为该方法是在指针接收器上(应该是这样),所以请使用切片元素的地址:

entity := dv.Index(i).Addr().Interface().(Queryable)

另一种方法是使用指针切片作为结果:

var result []*Camera
err := c.RunQuery(ctx, q, &result)

代码可以这样编写,以适用于 []Camera 或 []*Camera:

var queryableType = reflect.TypeOf((*Queryable)(nil)).Elem()
needAddr := !dv.Type().Implements(queryableType)

...

var entity Queryable
if needAddr {
entity = dv.Index(i).Addr().Interface().(Queryable)
} else {
entity = dv.Index(i).Interface().(Queryable)
}

英文:

Because the method is on a pointer receiver (as it should be), use the address of the slice element:

entity := dv.Index(i).Addr().Interface().(Queryable)

An alternative approach is to use a slice of pointers for the result:

var result []*Camera
err := c.RunQuery(ctx, q, &result)

The code can be written to work with both []Camera or []*Camera as follows:

var queryableType = reflect.TypeOf((*Queryable)(nil)).Elem()
needAddr := !dv.Type().Implements(queryableType)

...

var entity Queryable
if needAddr {
    entity = dv.Index(i).Addr().Interface().(Queryable)
} else {
    entity = dv.Index(i).Interface().(Queryable)
}

huangapple
  • 本文由 发表于 2015年12月7日 23:55:47
  • 转载请务必保留本文链接:https://go.coder-hub.com/34137787.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定