How would one provide a default interface, defined in terms of itself, to users?

agolangf · · 408 次点击    
这是一个分享于 的资源,其中的信息可能已经有所发展或是发生改变。
<p>This is basically what I want to do:</p> <pre><code>// Hypothetical checker for seeing if some set of keys are in a SQL DB type Checker interface { KeysInDb(keys []string) ([]bool, error) KeyInDb(key string) (bool, error) } // This is the weird part func (c Checker) KeyInDb(key string) (bool, error) { result, err := c.KeysInDb([]string{key}) if err != nil { return false, err } else { return result[0], nil } </code></pre> <p>Basically, I want to provide a default implementation for some of the methods on the database that depends on a &#34;core&#34; set of methods, that the user must provide. The user can also override the defaulted methods, but at a minimum they need the core set.</p> <p>I found <a href="https://www.reddit.com/r/golang/comments/28garc/what_is_the_preferred_way_to_provide_a_default/">this</a> old <a href="/r/golang">/r/golang</a> post about it: </p> <p>However, the proposed solution, <a href="https://play.golang.org/p/ByoUzfqOpn">here</a>, doesn&#39;t meet the property that the default implementations can use the core ones. For instance, I can&#39;t do this:</p> <pre><code>type DefaultChecker struct {} func (c DefaultChecker) KeyInDb(key string) (bool, error) { result, err := ???.KeysInDb([]string{key}) if err != nil { return false, err } else { return result[0], nil } type MyChecker struct { DefaultChecker ... } func (c MyChecker) KeysInDb(keys []string) ([]bool, error) { ... } </code></pre> <p>The closest thing I&#39;ve been able to do is this:</p> <pre><code>type CoreChecker interface { KeysInDb(keys []string) ([]bool, error) } type Checker struct { CoreChecker } func (c Checker) KeyInDb..... myChecker := Checker{MyChecker{...}} </code></pre> <p>However, this doesn&#39;t allow <code>MyChecker</code> to provide its own implementation of <code>KeyInDb</code> if it wants to. Plus, I&#39;m not a fan of the wrapper struct, when all I really want is a a single interface.</p> <p>Is there any (non-reflective) way to do what I&#39;m talking about? Any advice would be greatly appreciated.</p> <hr/>**评论:**<br/><br/>binaryblade: <pre><p>yeah</p> <pre><code>type MyChecker struct { Checker other data } // define any function you want on mychecker including your on KeyInDb </code></pre> <p>let the client use the core</p> <p>take an interface that you need, provide a struct which does what you think it should and let the client compose and embed to create a value which your library requires.</p></pre>Nathanfenner: <pre><p>You may be able to use a wrapper struct <em>instead of</em> an interface:</p> <pre><code>type Impl struct { KeyIn func(string) (bool, error) KeysIn func([]string) ([]bool, error) } type Checker struct { Impl } func (c Checker) KeysIn(keys []string) ([]bool, error) { if c.Impl.KeysIn == nil { result := []bool{} for _, k := range keys { v, err := c.KeyIn(k) if err != nil { return nil, err } result = append(result, v) } return result, nil } return c.Impl.KeysIn(keys) } </code></pre> <p>The main issue with the above API is that a client could fail to define one of the methods, and it would be a runtime error (no static checks). You could do better by having <code>Impl</code> itself embed an interface value supporting the core operations.</p></pre>danredux: <pre><p>This is about providing a library that will extend a user-defined type.</p> <p>Your last example is exactly what you&#39;re asking for, though. Think about it like this: Any &#34;wrapper&#34; you create will need to define an interface of &#34;core methods&#34; that it needs, so we already know that it&#39;s going to look something like this:</p> <pre><code>type CoreORM interface { KeysInDb(keys []string) ([]bool, error) } </code></pre> <p>Now, something has to hold what we pass it, obviously. Closure or struct, take your pick.</p> <pre><code>type ExtendedORM struct { Child CoreORM } </code></pre> <p>Now I suppose what you&#39;re missing is the last step, how to allow users to override methods. Well there&#39;s two things. First, why do you need to do this? The user can keep a reference to their &#34;ORM&#34; object and query it directly. The only thing I can see that may not be satisfying you is that methods on the ExtendedORM struct won&#39;t have access to overridden methods... In your example, the user ORM struct also defines a &#34;KeyInDb&#34; method, so the ExtendedORM struct should use it.</p> <p>This isn&#39;t hard to do at all, though. The idea here is to first check if the child has implemented the method and if so, proxy through to it. Otherwise, use the default behaviors. The ExtendedORM&#39;s KeyInDb method should just check to see if the child has overridden it.</p> <pre><code>func (o *ExtendedORM) KeyInDb(key string) (bool, error) { if proxy, yes := o.Child.(interface{KeyInDb(string)(bool, error)}); yes { return proxy.KeyInDb(key) } if result, err := o.Child.KeysInDb([]string{key}); err != nil { return false, err } else { return result[0], nil } } </code></pre> <p>Hope this helps. :)</p></pre>danredux: <pre><p>I just wanted to add, it&#39;s weird that the other solutions here - including the ones being upvoted - all failed to actually answer the question and are all instead suggesting other things.</p> <p>Safely asserting one interface as another is a way of &#34;testing&#34; a type to see if it has methods. This allows you to have a superclass &#34;check&#34; the child, proxying methods if the child has the method, but providing its own implementation if the child does not.</p> <p>It&#39;s a powerful feature and I feel either the others have not understood your question or do not understand type assertions.</p></pre>metakeule: <pre><p>To answer your question: One way to do it: <a href="https://play.golang.org/p/FwScPS_T2a" rel="nofollow">https://play.golang.org/p/FwScPS_T2a</a></p> <p>but probably sufficient: <a href="https://play.golang.org/p/xVOMRAElbC" rel="nofollow">https://play.golang.org/p/xVOMRAElbC</a></p></pre>jerf: <pre><p>Others have covered the technical details, so I&#39;ll make a meta-comment. You&#39;re basically asking how to provide a superclass that users can inherit from. But you&#39;re encountering the problem that Go doesn&#39;t have built-in inheritance, so you can&#39;t provide such a thing. The good news is, precisely because Go doesn&#39;t provide such a thing, your users don&#39;t expect it! It&#39;s OK to provide a struct with default implementations that your users have to compose in manually, or any of the other suggestions others have posted.</p> <p>I can make yet another suggestion to add to the list of things to think about. Break up the interface:</p> <pre><code>type KeyChecker interface { KeyInDb(key string) (bool, error) } type KeysChecker interface { KeysInDb(keys []string) ([]bool, error) } </code></pre> <p>You can leave the <code>KeyChecker</code> undefined, and then provide a default converter:</p> <pre><code>type KeyToKeysChecker func([]string) ([]bool, error) func (ktkc KeyToKeysChecker) KeysInDb(keys []string) ([]bool, error) { return ktkc(keys) } func ConvertKeyToKeys(kc KeyChecker) KeysChecker { return KeyToKeysChecker(func (keys []string) ([]bool, error) { var results []bool for _, key := range keys { b, err := kc(key) if err != nil { return nil, err } results = append(results, b) } return results, nil }) } </code></pre> <p>In the case of an SQL query that will result in one query per key, so you&#39;d want a note about it being a bad idea to use that with a lot of keys. With an in-memory DB that may not be so bad. Really I&#39;m just posting it as an example of the technique, which is generally useful.</p> <p>The next natural thought is that you might object that you&#39;d really like to be able to write a function then that either takes a <code>KeyChecker</code> or a <code>KeysChecker</code> and Go would make you use an <code>interface{}</code> for that, to which I&#39;d say again, a Go programmer won&#39;t expect their functions to be that flexible. Have a function state exactly which one it&#39;s going to use, and it&#39;s OK for the user to have to figure out how to supply such a value. It&#39;s OK in Go to expect the caller to do a bit more work than they might have to in Python or something.</p></pre>dchapes: <pre><p>A number of &#34;answers&#34; talk about &#34;super-classes&#34; and &#34;wrappers&#34; and stuff but given your initial example I see absolutely no reason why it shouldn&#39;t be:</p> <pre><code>type Checker interface { KeysInDb(keys []string) ([]bool, error) } func CheckOneKey(c Checker, key string) (bool, error) { b, err := c.KeysInDb([]string{key}) // ... } </code></pre> <p>Or probably this instead (a simpler API/interface is better):</p> <pre><code>type Checker interface { KeyInDb(key string) (bool, error) } func CheckMany(c Checker, keys []string) ([]bool, error) { result := make([]bool, 0, len(keys)) for i, k := range keys { b, err := c.KeyInDb(k) if err != nil { // Do you want to return partial results or not? return result, err } result = append(result, b) } return result, nil } </code></pre> <p>In other words, don&#39;t make the mistake of thinking everything needs to be a method.</p></pre>j7b: <pre><pre><code>type KeysFunc func(keys []string) ([]bool, error) func (kf KeysFunc) KeysInDb(keys []string) ([]bool, error) { return kf(keys) } func (kf KeysFunc) KeyInDb(key string) (bool, error) { result, err := kf.KeysInDb([]string{key}) if err != nil { return false, err } return result[0], nil } </code></pre> <p>but as noted elsewhere consider providing default implementations as functions or provide a wrapper to return struct{I1,I2} with a default and provided implementation.</p></pre>

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

408 次点击  
加入收藏 微博
暂无回复
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传