Interfaces vs Inheritance : practical question

blov · · 144 次点击    
这是一个分享于 的资源,其中的信息可能已经有所发展或是发生改变。
<p>Hey all. I have been reading the interwebs today a lot of interfaces (+embedded structs) vs inheritance (like how Python might do it). Please be easy on me, I am just starting out with Golang (but love it so far).</p> <p>This example is really contrived, but I think it demonstrates the mental roadblock I am facing. The following code does work, but it doesn&#39;t really like good Go idiomatic code. Lets say I have a base struct (that will at some point be marshaled into JSON)...I then want to &#34;inherit&#34; that struct into 3 different kinds (Workloads, Benchmarks, and Tests)...I want these different Structs because, while they might share the same function named Export(), they might have different initialization routines. </p> <p>So in some cases, I want to share routines on the receivers...but in other cases, I want an &#34;override&#34;. I think I am a bit stuck on my content vs behavior way of seeing things, but maybe someone can help me see the light? I want to write Go code that is above reproach :)</p> <pre><code>package main import ( &#34;fmt&#34; ) type SamplePackage struct { Name string `json:&#34;name&#34;` Tar string `json:&#34;tar&#34;` Compressed string `json:&#34;compressed&#34;` Path string `json:&#34;-&#34;` } type SamplePackages []SamplePackage func (s SamplePackages) Init() error { fmt.Println(&#34;Doing some super fancy init!&#34;) return nil } type Workloads struct { SamplePackages } type Benchmarks struct { SamplePackages } func (b Benchmarks) Init() error { fmt.Println(&#34;Do some excessively fancy inits&#34;) return nil } type Tests struct { SamplePackages } func main() { w, b, t := new(Workloads), new(Benchmarks), new(Tests) w.Init() b.Init() t.Init() } </code></pre> <p>The output from the above code is</p> <pre><code>Doing some super fancy init! Do some excessively fancy inits Doing some super fancy init! </code></pre> <p>In python, I would have done this like </p> <pre><code>Class SamplePackage: def Init(self): #yeah, Init was a really bad function name to use in hindsight... print &#34;Doing some super fancy Init!&#34; Class Tests(SamplePackage): pass Class Benchmarks(SamplePackage): print &#34;Do some excessively fancy inits&#34; </code></pre> <p>Thanks so much for anyone that can help me out here! I didn&#39;t even bother to try to add an interface to this because I didn&#39;t even see how it would improve anything (with my current implementation anyway)</p> <hr/>**评论:**<br/><br/>adawalli: <pre><p>Here is a link to the code if you are feeling extra lazy :) <a href="https://play.golang.org/p/jT9cNtRAhn9" rel="nofollow">https://play.golang.org/p/jT9cNtRAhn9</a></p></pre>: <pre><p>[deleted]</p></pre>solid7: <pre><p>That&#39;s your fault not theirs. Open the link in a browser.</p></pre>leaf_bebop: <pre><p>I was referring to the super lazy part. Was I unclear?</p></pre>adawalli: <pre><p>Sorry, if you have a better place that I can post it formatted, I would be happy to do so. I was only joking about the lazy part!</p></pre>leaf_bebop: <pre><p>With no further design aim given, I would say current code is not bad in terms of Idiomatic Go. But I am not very comfortable with the idea of embedding a slice in a struct, though it might not be the point.</p> <p>However, you should know that w.Init is calling w.SamplePackages.Init. And any codes inside that call would have no knowledge about the &#34;parent&#34; struct, including new calls. This may cause unexpected behavior for new gophers.</p> <p>For further discussion, say, if you need to have some common behavior of different types, use a function that accepts an interface. If you need to keep it as a method of the type, wrap the function call. I am sadly on mobile and cannot provide codes for that. Maybe later.</p> <p>Edit: I think I am over general on the part of embedding a slice in a struct.</p> <p>Type has behaviors beside methods. Slice has index, and struct has field. While it makes sense in promoting methods to utilize that, it is inexpressive and sometimes confusing to have inconsistent behavior in similar and theoretically parell types. In OP&#39;s case, when acquring an element, it is sps[0] vs. wl.SamplePackages[0]. I would say, it would be much more idomatic if they are both x.Packages[0], even though Go does not support attribute interface.</p> <p>However there are cases that are fine about embbeding slice or whatever type into the struct. If the embedded type only matters because of its method, or is purely and only a small part of the struct, it is fine. It avoids the behavior I described above.</p></pre>adawalli: <pre><p>Sure, any code you can provide would be great. What do you recommend that would be cleaner than a slice in the struct? I know I am going to have multiple packages of type package, what do you think might be the best way to store? Thanks again!</p></pre>TheMerovius: <pre><blockquote> <p>But I am not very comfortable with the idea of embedding a slice in a struct</p> </blockquote> <p>I see no problem with that. You are embedding a declared type, to get its methods promoted to the embedding struct; how that type is implemented doesn&#39;t really matter and you shouldn&#39;t concern yourself with that.</p> <p>The only thing I can think of that doesn&#39;t make sense to embed would be type-literals (as they can&#39;t have methods to promote), but apart from this, anything should go.</p></pre>leaf_bebop: <pre><p>Type has behaviors beside methods. Slice has index, and struct has field. While it makes sense in promoting methods to utilize that, it is inexpressive and sometimes confusing to have inconsistent behavior in similar and theoretically parell types. In OP&#39;s case, when acquring an element, it is sps[0] vs. wl.SamplePackages[0]. I would say, it would be much more idomatic if they are both x.Packages[0], even though Go does not support attribute interface.</p></pre>TheMerovius: <pre><blockquote> <p>Type has behaviors beside methods.</p> </blockquote> <p>But embedding is (mainly) about those.</p> <blockquote> <p>In OP&#39;s case, when acquring an element, it is sps[0] vs. wl.SamplePackages[0].</p> </blockquote> <p>Ah, so your issue isn&#39;t &#34;embedding slices vs. embedding something else&#34;, but &#34;embedding slices vs. not embedding slices&#34;. Fair enough. I still think it&#39;s fine to embed to selectively overwrite methods. Yes, I agree that this will make it impossible to index into the embedding type, but more often than not that&#39;s an inconsequential restriction.</p></pre>leaf_bebop: <pre><p>Well, if SapmplePackages is a struct with a slice field, like type SpamplePackages struct { Pkgs []whatever }, embedding will not cause any trouble. You can write uniformly x.Pkgs[0]. </p></pre>TheMerovius: <pre><p>I don&#39;t understand this argument. This way you&#39;re going to have to write <code>x.SamplePackages[0]</code>, so what? Also, what if OP doesn&#39;t even <em>care</em> about indexing?</p> <p>Don&#39;t get me wrong, there are probably situations where one or the other makes sense, but taking the position that slice-types shouldn&#39;t be embedded is pretty weird to me. They are types like any else, if you want their methods being promoted, embed them like any else.</p></pre>leaf_bebop: <pre><p>Readability and expressiveness, that&#39;s all. OP asks for idiomatic Go code, not how to promote methods. </p> <p>You can embed everything as long as the compiler does not complain about it (for now, it&#39;s everything except pointer types) as you wish, as you feel like. And you may simple not care about indexing, or any other problem it may cause, as long as the code compile and run.</p> <p>I feel I have completed my arguments, so I probably won&#39;t reply this again.</p></pre>xuu0: <pre><p>There are plenty of examples of slice types in the standard library. Using the sort methods requires a slice type that implements the sort.Interface</p> <p><a href="https://golang.org/pkg/sort/#Interface" rel="nofollow">https://golang.org/pkg/sort/#Interface</a></p> <p><a href="http://golang.org/pkg/net/#IP" rel="nofollow">http://golang.org/pkg/net/#IP</a></p> <p><a href="http://golang.org/pkg/sort/#Float64Slice" rel="nofollow">http://golang.org/pkg/sort/#Float64Slice</a></p> <p><a href="http://golang.org/pkg/sort/#IntSlice" rel="nofollow">http://golang.org/pkg/sort/#IntSlice</a></p> <p><a href="http://golang.org/pkg/encoding/json/#RawMessage" rel="nofollow">http://golang.org/pkg/encoding/json/#RawMessage</a></p></pre>leaf_bebop: <pre><p>I did not recite all these code, but as far as I can tell, there are no <em>embedding a slice into a struct</em>, is there?</p></pre>xuu0: <pre><p>When it comes to the language itself it doesn&#39;t matter if a embedded type is a slice or a string or whatever. </p> <p>Take the example of an IP which is defined as <code>type []byte</code>. Say you wanted to use that to make a type that represented a service IP and port?</p> <pre><code>type Service struct { net.IP Port int } </code></pre> <p>This is perfectly fine. Because the type is composed with the IP you can now use the methods on that type. Like using String() to print out a formatted ip address. Or ParseIP() to read in an IPv4 or IPv6 address. </p> <pre><code>s := Service{net.ParseIP(&#34;2001:480:12::1&#34;), 80} fmt.Printf(&#34;%s/%d&#34;, s, s.Port) // 2001:480:12::1/80 </code></pre></pre>leaf_bebop: <pre><p>You can not say because it compiles (and it runs correctly) it is fine. </p> <p>In your example, you do not benefit from ParseIP as you can write the same code with net.IP as an un-embedded field.</p> <p>And, the Printf part is worse. It would take a while for anyone who is not familiar with the Service type to understand what is happening here and why is s as a whole (or it seems) and s.Prot being printed at the same time. Sure you can argue it is easy to find out once you run the code, but not all code is at an easily runnable location (and it does not change the fact the code is not idmoatic).</p> <p>The problem here, however, I think is over-using composition, instead of embedding a slice. On that part, I think my other comments is sufficent for my statement.</p></pre>shovelpost: <pre><p>It is not easy to help you since you are not describing the problem you are trying to solve very well. Sure you&#39;re saying you want to &#34;inherit&#34; but then you might already using a mindset which might not be appropriate for the problem.</p> <p>Then it sounds like you have already created a working solution. What is the problem?</p></pre>TheMerovius: <pre><p>IMO, the code is fine as-is. That is exactly what embedding is for.</p></pre>

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

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