if class hierarchies aren't necessary, why are interfaces?

xuanbao · · 440 次点击    
这是一个分享于 的资源,其中的信息可能已经有所发展或是发生改变。
<p>EDIT: resolved, see bottom.</p> <p>Original post:</p> <hr/> <p>Hi,</p> <p>I&#39;ve just started learning Go. </p> <p>I know a key design constraint was a minimum specification that could be kept in mind completely. (&#34;less is exponentially more.&#34; - Rob Pike)</p> <p>I greatly appreciate that it does not include large class hierarchies and that I won&#39;t be expected to have to invent any.</p> <p>I am trying to understand interfaces and what necessitates it now.</p> <p>Could someone offer the following exposition:</p> <ul> <li><p>suppose interfaces were not in the language. What would be impossible, yet absolutely necessary even for writing straightforward procedural code?</p></li> <li><p>could you show short but real examples where it&#39;s absolutely necessary?</p></li> </ul> <p>What &#34;required&#34; the authors to have to add it?</p> <p>I don&#39;t see it yet, as I have now written Go programs but I did not use interfaces.</p> <p>Please include deriving/defining it, as I clearly don&#39;t really understand this feature yet (what it means, what it does), including at a syntactic level.</p> <p>I hope you understand my question.</p> <p>Thank you.</p> <hr/> <p>EDIT:</p> <p>cs-guy&#39;s link below</p> <ul> <li><a href="https://8thlight.com/blog/javier-saldana/2015/02/06/loose-coupling-in-go-lang.html" rel="nofollow">https://8thlight.com/blog/javier-saldana/2015/02/06/loose-coupling-in-go-lang.html</a></li> </ul> <p>is exactly (precisely) the exposition I asked for, including teaching the syntax, and completely answers my question. Thank you!</p> <hr/>**评论:**<br/><br/>cs-guy: <pre><p>Interfaces are not required for writing straightforward procedural code. Interfaces in Go are the primary means for decoupling components of our programs. Tight <a href="https://en.wikipedia.org/wiki/Coupling_(computer_programming)" rel="nofollow">coupling</a> between modules generally makes them less reusable, more fragile, and more difficult to test. We therefore try to <a href="https://en.wikipedia.org/wiki/Loose_coupling" rel="nofollow">minimize the coupling</a> between modules, and interfaces are a perfect tool for that.</p></pre>gobegin: <pre><p>Hi, OP here. This is a good high-level (abstract) explanation that aided in my understanding. (And you replied within a few minutes of my post.) Thank you.</p> <p>As I am still also learning semantics and syntax, if you or someone else could reply with the kind of &#34;textbook&#34; exposition that sort of derives it, from illustrative examples of what would happen if it did not exist, I would appreciate it.</p> <p>I know this takes a bit more work than the high-level explanation, but this kind of derivation is missing from the references I am using.</p> <p>For example, even your statement that it is not necessary for straightforward procedural code, took me by surprise.</p> <p>I would like a derivation/exposition with code samples that show how everything falls apart without it. I know I&#39;m asking for a lot. :)</p></pre>cs-guy: <pre><p>Here are a few articles that might help you along your path of understanding this topic:</p> <ul> <li><a href="https://8thlight.com/blog/javier-saldana/2015/02/06/loose-coupling-in-go-lang.html" rel="nofollow">https://8thlight.com/blog/javier-saldana/2015/02/06/loose-coupling-in-go-lang.html</a></li> <li><a href="https://appliedgo.net/di/" rel="nofollow">https://appliedgo.net/di/</a></li> <li><a href="https://dave.cheney.net/2016/08/20/solid-go-design" rel="nofollow">https://dave.cheney.net/2016/08/20/solid-go-design</a></li> </ul> <p>I said that interfaces are not needed for writing straightforward procedural code because Go would still be a fully capable language without interfaces. But without interfaces it would be more difficult to write modular, flexible, maintainable, testable code in Go. Interfaces let our programs scale in size more gracefully, but they are not, strictly speaking, necessary to write working software.</p></pre>adelowo: <pre><p>Plus one for solid go design</p></pre>gobegin: <pre><p>I found your first link to be precisely the exposition I asked. It is exactly what I sought. I am adding it as an accepted answer to my post.</p> <p>(Your second link, with the poetry, is the type of example I already had. The third link is more abstract).</p> <p>So to reiterate, your first link is exactly what I sought and fully answers my question.</p> <p>Thank you.</p></pre>shadowmint: <pre><p>You may find these links helpful:</p> <p><a href="https://medium.com/@cep21/preemptive-interface-anti-pattern-in-go-54c18ac0668a" rel="nofollow">https://medium.com/@cep21/preemptive-interface-anti-pattern-in-go-54c18ac0668a</a></p> <p><a href="https://medium.com/@cep21/what-accept-interfaces-return-structs-means-in-go-2fe879e25ee8" rel="nofollow">https://medium.com/@cep21/what-accept-interfaces-return-structs-means-in-go-2fe879e25ee8</a></p> <p>I think one of the more compelling uses of interfaces in go is as a way of implementing &#39;generic&#39; behavior. </p> <p>Consider:</p> <pre><code>type MyAction struct { } func (*MyAction) foo() bool { return true } func execute(action *MyAction) bool { return action.foo() } </code></pre> <p>If you have two actions, you need <code>executeMyAction</code> and <code>executeMyOtherAction</code>; or, you can do this:</p> <pre><code>type MyAction struct { } func (*MyAction) foo() bool { return true } type MyOtherAction struct { } func (*MyOtherAction) foo() bool { return false } type Action interface { foo() bool } </code></pre> <p>This sort of behavior would be extremely difficult to implement without interfaces.</p> <p>Notice that unlike say, java or C#, you don&#39;t have to <em>opt in</em> to interfaces in go; it&#39;s like duck typing in python; anything that implements <code>foo() bool</code> is now Action, and can be used by <code>execute</code>. I feel like this is really the key distinction about go interfaces that makes them useful and powerful as an alternative to what you would in another language.</p> <p>As a practical example, testing is extremely irritating without interfaces, because the &#39;real&#39; implementation is the only way to test things. It makes unit testing basically impossible in complex systems.</p> <p>...but, just in straight normal code, say, inside a function... there&#39;s not really any compelling reason for interfaces to exist. They&#39;re primarily for applying compile time type assertions about what enters and leaves functions in your code.</p></pre>stone_henge: <pre><p>Interfaces are convenient, but the behavior you are implementing is far from extremely difficult to implement otherwise. First, using interfaces:</p> <pre><code>package main import &#34;log&#34; type MyAction struct{} type MyOtherAction struct{} func (*MyAction) foo() bool { return true } func (*MyOtherAction) foo() bool { return false } type Action interface { foo() bool } func execute(action Action) bool { return action.foo() } func main() { var a MyAction var b MyOtherAction log.Println(execute(&amp;a)) log.Println(execute(&amp;b)) } </code></pre> <p>Using a function pointer, which is possible with a trivial example:</p> <pre><code>package main import &#34;log&#34; type MyAction struct{} type MyOtherAction struct{} func (*MyAction) foo() bool { return true } func (*MyOtherAction) foo() bool { return false } func execute(action func() bool) bool { return action() } func main() { var a MyAction var b MyOtherAction log.Println(execute(a.foo)) log.Println(execute(b.foo)) } </code></pre> <p>In Go you can use function pointers, because the receivers are passed along with them implicitly. You can use closures to the same effect.</p> <p>Then, in C, using plain structs:</p> <pre><code>#include &lt;stdio.h&gt; typedef struct { void *receiver; int (*foo)(void *); } ActionInterface; typedef struct { } MyAction; typedef struct { } MyOtherAction; int MyAction_foo(void *a) { return 1; } int MyOtherAction_foo(void *a) { return 0; } int execute(ActionInterface a) { return a.foo(a.receiver); } int main(void) { MyAction a; MyOtherAction b; ActionInterface a_wrapped = { &amp;a, MyAction_foo }; ActionInterface b_wrapped = { &amp;b, MyOtherAction_foo }; printf(&#34;%d\n&#34;, execute(a_wrapped)); printf(&#34;%d\n&#34;, execute(b_wrapped)); return 0; } </code></pre> <p>Of course we don&#39;t really need the receiver pointer for such a simple example, but this works generally for any number of methods and arbitrary struct fields.</p></pre>shadowmint: <pre><p>This is obviously a contrived example. </p> <p>How are you going to do this in your example? </p> <p><a href="https://play.golang.org/p/BRaXpcl1g9" rel="nofollow">https://play.golang.org/p/BRaXpcl1g9</a></p> <pre><code>package main import ( &#34;fmt&#34; ) type Foo struct { } type Bar struct { } type Item interface { } type Action interface { execute() } func (self *Foo) execute() { fmt.Println(&#34;Hello&#34;) } func executeIfAction(i Item) { if action, ok := i.(Action); ok { action.execute() } } func main() { executeIfAction(Foo{}) executeIfAction(&amp;Foo{}) // &lt;--- Notice only this executes executeIfAction(&amp;Bar{}) } </code></pre> <p>Not so simple as function pointers is it?</p> <p>Sure, you can create an <code>item</code> method on Foo and Bar that returns a function pointer if it implements action... but really, you&#39;re just manually implementing the same mechanic as interfaces provide at that point.</p> <p>So... if you prefer to manually do that, go for it; but that&#39;s why interfaces are useful.</p></pre>stone_henge: <pre><blockquote> <p>Not so simple as function pointers is it?</p> </blockquote> <p>I didn&#39;t say that simply passing funcion pointers would solve the general case. Your example is still simple enough that you can use function pointers, though.</p> <blockquote> <p>Sure, you can create an item method on Foo and Bar that returns a function pointer if it implements action... but really, you&#39;re just manually implementing the same mechanic as interfaces provide at that point.</p> </blockquote> <p>Yes, my point isn&#39;t that you should, it&#39;s that it isn&#39;t extremely difficult, and that someone interested in how it works might benefit from an explanation that doesn&#39;t allude to the extreme difficulty of the use case it solves. Interfaces are in essence structs of a receiver pointer, a bunch of function pointers and a conrete type enum. It&#39;s a common pattern in C.</p></pre>shadowmint: <pre><p>I think how this relates to C is basically irrelevant, but I see your point.</p> <p>&#39;extreme difficulty&#39; is perhaps an over exaggeration, but I think you&#39;re being pedantic for the sake of it. Interfaces make code simpler.</p> <p>...but yes, indeed, you don&#39;t have to use them. As with basically all other abstractions, you can always just roll your own version of the same thing if you don&#39;t want to use the commonly used version for... whatever reason. </p> <p>(heck, look at the crazy stuff you can do in rust faking object pointers to create a linked list using an array of index ints to avoid the borrow checker problems. /shrug; you&#39;re just trading complexity that other people have abstracted for complexity that you&#39;re writing by hand, yourself)</p></pre>stone_henge: <pre><blockquote> <p>I think how this relates to C is basically irrelevant, but I see your point.</p> </blockquote> <p>Taking the feature OP is confused about and implementing it in a language that doesn&#39;t feature such high level abstractions can be a more useful way of explaining it than conceding that the problem it solves is hard.</p> <blockquote> <p>&#39;extreme difficulty&#39; is perhaps an over exaggeration, but I think you&#39;re being pedantic for the sake of it.</p> </blockquote> <p>I can&#39;t account for your suspicions, and I&#39;m frankly not interested. Can we please stay on topic?</p> <blockquote> <p>Interfaces make code simpler.</p> </blockquote> <p>Agreed, and I&#39;ve never implied otherwise.</p> <blockquote> <p>...but yes, indeed, you don&#39;t have to use them. As with basically all other abstractions, you can always just roll your own version of the same thing if you don&#39;t want to use the commonly used version for... whatever reason.</p> </blockquote> <p>In this case, the reason is discussion. OP is asking us to &#34;suppose interfaces were not in the language&#34; for the sake of discussing the feature, what problems it solves and how.</p></pre>shadowmint: <pre><blockquote> <p>implementing it in a language that doesn&#39;t feature such high level abstractions can be a more useful way of explaining it than conceding that the problem it solves is hard.</p> </blockquote> <p>Since you can implement it just as well in go to demonstrate the point, that seems, as I said, entirely irrelevant; you&#39;re just introducing more complexity into the topic by bring a whole new language into it. Who cares if you can implement it in C?</p> <blockquote> <p>Can we please stay on topic?</p> </blockquote> <p>We&#39;ll just have to disagree.</p></pre>stone_henge: <pre><blockquote> <p>Since you can implement it just as well in go, that seems, as I said, entirely irrelevant; you&#39;re just introducing more complexity into the topic by bring a whole new language into it.</p> </blockquote> <p>I think that C is great for demonstrating these concepts, because its design is rather simple and ubiquitous. Anyone with half a CS degree will have used C at some point, while a self identified go beginner may not be entirely familiar with the language.</p> <p>That said, you can do it in Go almost like you would in C by using <code>unsafe</code>. Here&#39;s a bit more complex of an example (two method interface):</p> <pre><code>package main import ( &#34;fmt&#34; &#34;unsafe&#34; ) type ActionInterface struct { receiver unsafe.Pointer foo func(unsafe.Pointer) int bar func(unsafe.Pointer) string } type MyAction struct { val float32 greeting string } type MyOtherAction struct{ val int } func MyAction_foo(a unsafe.Pointer) int { return int((*MyAction)(a).val) } func MyAction_bar(a unsafe.Pointer) string { return fmt.Sprintf(&#34;%s, World&#34;, (*MyAction)(a).greeting) } func MyOtherAction_foo(a unsafe.Pointer) int { return (*MyOtherAction)(a).val } func MyOtherAction_bar(a unsafe.Pointer) string { return &#34;asdf&#34; } func execute(a ActionInterface) int { fmt.Println(a.bar(a.receiver)) return a.foo(a.receiver) } func main() { a := MyAction{7.5, &#34;Good day&#34;} b := MyOtherAction{1000} a_wrapped := ActionInterface{unsafe.Pointer(&amp;a), MyAction_foo, MyAction_bar} b_wrapped := ActionInterface{unsafe.Pointer(&amp;b), MyOtherAction_foo, MyOtherAction_bar} fmt.Printf(&#34;%d\n&#34;, execute(a_wrapped)) fmt.Printf(&#34;%d\n&#34;, execute(b_wrapped)) } </code></pre> <p>You could do away with another level of abstraction in Go, since receivers are implicitly passed along with function pointers:</p> <pre><code>package main import &#34;fmt&#34; type ActionInterface struct { foo func() int bar func() string } type MyAction struct { val float32 greeting string } type MyOtherAction struct{ val int } func (a *MyAction) foo() int { return int(a.val) } func (a *MyAction) bar() string { return fmt.Sprintf(&#34;%s, World&#34;, a.greeting) } func (a *MyOtherAction) foo() int { return a.val } func (a *MyOtherAction) bar() string { return &#34;asdf&#34; } func execute(a ActionInterface) int { fmt.Println(a.bar()) return a.foo() } func main() { a := MyAction{7.5, &#34;Good day&#34;} b := MyOtherAction{1000} fmt.Printf(&#34;%d\n&#34;, execute(ActionInterface{a.foo, a.bar})) fmt.Printf(&#34;%d\n&#34;, execute(ActionInterface{b.foo, b.bar})) } </code></pre> <p>Of course, since you also have closures, you could use that, too:</p> <pre><code>package main import &#34;fmt&#34; type ActionInterface struct { foo func() int bar func() string } type MyAction struct { val float32 greeting string } type MyOtherAction struct{ val int } func ActionInterfaceFromMyAction(a *MyAction) ActionInterface { return ActionInterface{ func() int { return int(a.val) }, func() string { return fmt.Sprintf(&#34;%s, World&#34;, a.greeting) }, } } func ActionInterfaceFromMyOtherAction(a *MyOtherAction) ActionInterface { return ActionInterface{ func() int { return a.val }, func() string { return &#34;asdf&#34; }, } } func execute(a ActionInterface) int { fmt.Println(a.bar()) return a.foo() } func main() { a := MyAction{7.5, &#34;Good day&#34;} b := MyOtherAction{1000} fmt.Printf(&#34;%d\n&#34;, execute(ActionInterfaceFromMyAction(&amp;a))) fmt.Printf(&#34;%d\n&#34;, execute(ActionInterfaceFromMyOtherAction(&amp;b))) } </code></pre></pre>tylermumford: <pre><p>Keep in mind that nothing about programming languages is strictly necessary. Anything other than straight machine code is simply a convenience for us humans, so every feature of every language is, on some level, just syntactic sugar.</p> <p>This doesn&#39;t answer your question, but hopefully it provides some context and perspective to someone.</p></pre>

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

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