Interfaces... I don't get them.

xuanbao · · 547 次点击    
这是一个分享于 的资源,其中的信息可能已经有所发展或是发生改变。
<p>I come from JavaScript/Node.js. (Did that immediately take away 90% of the crowd?)</p> <p>I&#39;ve been programming for maybe 8 months and have tried a variety of languages. This includes a few weeks in C/C++, which was very unenjoyable. I decided to focus my time to Go to see if I liked it after a few weeks.</p> <p>I understand Structs for the most part. Interfaces, I don&#39;t get why. </p> <p>If you have a square and a circle, and want to get the area for each, you still have to define the function twice, to work with both structs. I don&#39;t see how it saves work or really makes things neater at all.</p> <p>Most of the examples I&#39;ve seen show, &#34;This is how Java would do the same thing..&#34; Unfortunately, I didn&#39;t get very deep in to Java, so that&#39;s no help to me.</p> <p>Can someone explain why we need structs, and how to implement them, in a &#34;For Dummies&#34; way?</p> <p>Loving Go, this is the only confusing part so far. </p> <hr/>**评论:**<br/><br/>natefinch: <pre><p>Go&#39;s interfaces make it work like dynamically typed languages. You can write a function that takes a value and calls a method on that value.... you don&#39;t care what it&#39;s type is, as long as it has that method.</p> <p>This is just like writing a function in javascript. You write a function, it takes some argument, you assume it has a method you can call on it, and if it doesn&#39;t there&#39;s a failure at runtime. </p> <p>Go just formalizes this so that you can have compile-time checking to make sure the value you pass into the function actually has that method, so there&#39;s never a runtime failure.</p> <p>The idea is that interfaces make your functions more flexible, more easily reused. Let&#39;s say you have a method that computes how much paint you need to cover N surfaces:</p> <pre><code>func PaintTotal(surfaces []Surface) int { total := 0 for _, s := range surfaces { total += s.Area() * some_factor } return total } type Surface interface { Area() int } </code></pre> <p>Thus, you could pass in a Circle, a Square, a Car, anything that has an <code>Area() int</code> method on it, and this function will tell you how much paint you need to cover the entire thing.</p></pre>bigpigfoot: <pre><p>The best example to illustrate this is the Stringer interface declared in the fmt package. Implementing the String() method of that interface lets you specify what gets printed.</p></pre>natdm: <pre><p>Thanks! This is pretty helpful. I think I understand it a bit more. I&#39;ll have to force myself to use them for a bit.</p></pre>headzoo: <pre><blockquote> <p>I don&#39;t see how it saves work</p> </blockquote> <p>This is where beginners always go wrong with interfaces. Interfaces aren&#39;t meant to save you from typing more. So get that out of your head.</p> <blockquote> <p>If you have a square and a circle, and want to get the area for each, you still have to define the function twice</p> </blockquote> <p>How would you display the area of each? Let&#39;s start by defining our structs.</p> <pre><code>type Square struct { Base float32 } func (self Square) Area() float32 { return self.Base * self.Base } type Circle struct { Radius float32 } func (self Circle) Area() float32 { return 3.14 * (self.Radius * self.Radius) } </code></pre> <p>Now you need functions to display the area. You might write code like this.</p> <pre><code>func PrintSquareArea(s Square) { fmt.Printf(&#34;The area is %f\n&#34;, s.Area()) } func PrintCircleArea(c Circle) { fmt.Printf(&#34;The area is %f\n&#34;, c.Area()) } </code></pre> <p>I hope you see the problem with this code. Both functions do exactly the same thing. Because Go is strongly typed we need to have two functions. One to display the area of squares, and one to display the area of circles. We can&#39;t write a single function that takes <em>both</em> a <code>Square</code> <em>or</em> <code>Circle</code>. Or can we?</p> <p>This is where interfaces come into play. Notice that both print functions are calling the <code>Area()</code> method on the given object. The functions don&#39;t care if the object has other methods. It only cares that the object has an <code>Area()</code> method. So we define an interface which has that method.</p> <pre><code>type Areable interface { Area() float32 } </code></pre> <p>Now we can throw away our two functions and write a single function that takes an instance of <code>Areable</code>.</p> <pre><code>func PrintArea(a Areable) { fmt.Printf(&#34;The area is %f\n&#34;, a.Area()) } </code></pre> <p>Here&#39;s our completed code.</p> <pre><code>type Areable interface { Area() float32 } type Square struct { Base float32 } func (self Square) Area() float32 { return self.Base * self.Base } type Circle struct { Radius float32 } func (self Circle) Area() float32 { return 3.14 * (self.Radius * self.Radius) } func PrintArea(a Areable) { fmt.Printf(&#34;The area is %f\n&#34;, a.Area()) } func main() { s1 := Square{Base: 6} c1 := Circle{Radius: 12} PrintArea(s1) PrintArea(c1) } </code></pre> <p>With interfaces we can pass an instance of <code>Square</code> <em>or</em> <code>Circle</code> to the <code>PrintArea()</code> function, because both the <code>Square</code> and <code>Circle</code> structs implement the <code>Areable</code> interface, which is the type <code>PrintArea()</code> expects.</p> <p>Note that unlike some (most) languages, your structs don&#39;t explicity declare that they implement the interface. You don&#39;t write code like this.</p> <pre><code>type Square struct { implements Areable Base float32 } </code></pre> <p>With Go the structs automatically implement the interface by virtue of having the same method signatures.</p></pre>jimmeyotoole: <pre><p>This is definitely the most clear answer. Appreciate your concise example! </p></pre>natdm: <pre><p>Interesting.. Just got a contrived example to work.</p> <p>Can you return multiple values from an interface function? I can&#39;t seem to get that to work.</p> <p>Got it to work. I found out what I was doing wrong.</p> <p>So it sounds like you can have multiple Structs have their own methods, and as long as those methods are named the same, you can tie that to an Interface. You can then have a function that reads from that interface.</p> <p>Messing around with it, I finally made my own (ridiculously silly example):</p> <p><a href="http://pastebin.com/pvNU7nmR" rel="nofollow">http://pastebin.com/pvNU7nmR</a></p></pre>no1youknowz: <pre><blockquote> <p><a href="http://pastebin.com/pvNU7nmR" rel="nofollow">http://pastebin.com/pvNU7nmR</a></p> </blockquote> <p>Playground example: <a href="http://play.golang.org/p/Kes8bA_t1w" rel="nofollow">http://play.golang.org/p/Kes8bA_t1w</a></p></pre>natdm: <pre><p>Woops. Thanks.</p></pre>headzoo: <pre><blockquote> <p>So it sounds like you can have multiple Structs have their own methods, and as long as those methods are named the same, you can tie that to an Interface.</p> </blockquote> <p>Yup. A really common interface you&#39;ll encounter in Go is <a href="https://golang.org/pkg/io/#Reader" rel="nofollow">io.Reader</a>. Lots of different objects can read things. For instance objects which represent an HTTP response, or an object which represent an open file, or an object which represents user input, etc, etc. As long as an object has the method <code>Read(p []byte) (n int, err error)</code> it automatically implements <code>io.Reader</code>, and can be passed to any function that requires an <code>io.Reader</code>.</p> <p>You&#39;ll see interfaces used a lot for abstractions, for instance a database abstraction. Imagine you have a database library that can connect to MySQL, or PostgreSQL, or SQLite. Each will have a struct for connecting to and querying the database server.</p> <pre><code>type MySQLConnection struct { } func (self *MySQLConnection) Open(host, user, pass string) error { // ... } func (self *MySQLConnection) Close() error { // ... } func (self *MySQLConnection) Query(sql string) ([]string, error) { // ... } type PostgreSQLConnection struct { } func (self *PostgreSQLConnection) Open(host, user, pass string) error { // ... } func (self *PostgreSQLConnection) Close() error { // ... } func (self *PostgreSQLConnection) Query(sql string) ([]string, error) { // ... } type SqlLiteConnection struct { } func (self *SqlLiteConnection) Open(filename string) error { // ... } func (self *SqlLiteConnection) Close() error { // ... } func (self *SqlLiteConnection) Query(sql string) ([]string, error) { // ... } </code></pre> <p>Each type of database has different connection requirements. MySQL and PostgreSQL each connect to a remote server, and need to know the host, user, and pass to the server, while SQLite reads from a file, and only needs to the know the path to the file. Once you&#39;ve created an instance of the connection struct, and opened a connection to the database, you&#39;ll probably need to pass the connection to other functions. Again, this is where interfaces come into play. We&#39;ll create an interface representing a database connection.</p> <pre><code>type DatabaseConnection interface { Query(sql string) ([]string, error) Close() error } </code></pre> <p>Now we can create methods which accepts any instance of <code>DatabaseConnection</code>. For instance a function to query the server and display the results.</p> <pre><code>func DisplayUsers(c DatabaseConnection) error { result, err := c.Query(&#34;SELECT * FROM users&#34;) if err != nil { return err } for _, row := range result { fmt.Println(row) } return nil } </code></pre> <p>Now we can connect to one of the three database types and query for a list of users.</p> <pre><code>func main() { conn := &amp;MySQLConnection{} err := conn.Open(&#34;localhost&#34;, &#34;root&#34;, &#34;foo&#34;) if err != nil { panic(err) } defer conn.Close() // or connect to a PostgreSQL database // conn := &amp;PostgreSQLConnection{} // err := conn.Open(&#34;remote-server&#34;, &#34;root&#34;, &#34;foo&#34;) // if err != nil { // panic(err) // } // defer conn.Close() // or open a SQLite database // conn := &amp;SqlLiteConnection{} // err := conn.Open(&#34;/home/joe/database&#34;) // if err != nil { // panic(err) // } // defer conn.Close() err := DisplayUsers(conn) if err != nil { panic(err) } } </code></pre> <p>The <code>DisplayUsers()</code> function doesn&#39;t need to know or care <em>what</em> type of database it&#39;s talking to. Hell, it doesn&#39;t even have to be talking to a database. The connection object could be talking to a remote REST API. The only thing the <code>DisplayUsers()</code> function cares about is using an object which implements the method <code>Query(sql string) ([]string, error)</code>.</p></pre>Xor_Boole: <pre><blockquote> <p>This is where beginners always go wrong with interfaces. Interfaces aren&#39;t meant to save you from typing more. So get that out of your head.</p> </blockquote> <p>To be honest, I think this is incorrect. Abstracting behaviors out to interfaces does save typing; instead you&#39;d have to write a method for each type with the behavior, or else take an <code>interface{}</code> argument and inspect/cast to something with the behavior.</p> <p>While it doesn&#39;t appear to save work at first, it does when you have a large number of things implementing the behavior, or when you want to make a publicly implementable interface.</p> <p>(Other than that, great post btw.)</p></pre>headzoo: <pre><p>Oh, I agree. I just don&#39;t want OP to get too hung up on seeing inheritance has a means to save typing and nothing else. Inheritance and interfaces <em>do</em> save typing, but they serve a more abstract (as in abstract thinking) purpose. The real power of inheritance comes along when you start thinking about <em>relationships</em> and created classes of objects.</p></pre>Xor_Boole: <pre><p>Compiletime checking is pretty useful, but a lot of people coming form dynamic languages might not realize how powerful it is immediately.</p> <p>Meanwhile, there&#39;s Rust and Scala with their Turing-complete type systems. Woo <code>() =&gt; f forSome {type f &lt;: () =&gt; f}</code>!</p></pre>lobster_johnson: <pre><p>An interface allows multiple structs to offer the same function(s) under one name. The caller only needs to know the interface, but not the underlying implementation.</p> <p>In your circle-and-square example, we start with this:</p> <pre><code>type Shape interface { // Area computes the 2D area of the shape. Area() float64 } </code></pre> <p>Now we can have this silly function:</p> <pre><code>func FindLargest(shapes []Shape) Shape { var largest Shape largestAreaSoFar := 0.0 for _, shape := range shapes { area := shape.Area() if area &gt; largestAreaSoFar { largest, largestAreaSoFar = shape, area } } return largest } </code></pre> <p>To call this function:</p> <pre><code>largest := FindLargest([]Shape{circle, square, rectangle}) </code></pre> <p>Notice how <code>FindLargest</code> doesn&#39;t need to know about circles, squares and rectangles. It only needs the interface, which gives it access to <code>Area()</code>.</p> <p>Interfaces allow you to create <em>abstractions</em>. Go isn&#39;t very keen on abstractions in general, but it would be a much poorer language indeed if it didn&#39;t have interfaces. Interfaces in Go are similar to interfaces in Java, with a crucial difference: Structs don&#39;t need to declare what interface they conform to. In Java you&#39;d say:</p> <pre><code>class Circle implements Shape { ... </code></pre> <p>In Go, interface implementations are inferred from which functions they implement. This can lead to some accidental behaviour, but for the most part, it works.</p> <p>As for <code>struct</code> — think of structs as (mostly) unordered groups of things:</p> <ul> <li><code>int</code>, <code>string</code>, <code>byte</code> etc. are individual things. The thing (e.g. the number 42 or the string &#34;Hello world&#34;) doesn&#39;t have a name.</li> <li>Arrays are lists of things. Each thing in the list doesn&#39;t have a name, only a position.</li> <li>Maps are lookup tables of things to other things. Think of a table of contents: Each title in the table has a page number.</li> <li>Structs are a bunch of things in a group, where each thing has been given a name. Think of structs as forms, where each field has a name: <code>dateOfBirth: 1984-07-03</code>, and so on.</li> </ul> <p>Interfaces are an abstraction of this, because an interface can represent any thing.</p></pre>dinkumator: <pre><p>It doesn&#39;t save effort on the structs themselves, it saves effort on their later composition. And for expansion into new shapes later.</p> <p>For example, here&#39;s a simple demo showing a function that calculates how many shapes you need to cover a given surface area: <a href="https://play.golang.org/p/L_mqlMXIke">https://play.golang.org/p/L_mqlMXIke</a></p></pre>djherbis: <pre><p>Effective Go is always a good start: <a href="https://golang.org/doc/effective_go.html#interfaces">https://golang.org/doc/effective_go.html#interfaces</a></p> <p>&#34;Interfaces in Go provide a way to specify the behavior of an object: if something can do this, then it can be used here.&#34;</p> <p>Interfaces allow a Go type to be reused in different contexts easily.</p> <p>I think the io.Reader and io.Writer interfaces are a great example. <a href="https://golang.org/pkg/io/#Reader">https://golang.org/pkg/io/#Reader</a> <a href="https://golang.org/pkg/io/#Writer">https://golang.org/pkg/io/#Writer</a></p> <p>There are lots of different structs which meet the io.Reader and io.Writer interfaces (network connections, files, buffers etc.). The cool thing about this is that it lets functions act on those structs purely as a function of their behavior as a Reader or a Writer.</p> <p>That&#39;s how we get fun things like io.Copy which can take bytes from anything with a Read method and pass them to anything with a Write method.</p> <p>It also let&#39;s us compose those behaviors with things like io.MultiWriter, which creates a new io.Writer which copies writes to multiple writers (like if you want to write an os.File and write to a hash function for a checksum: <a href="https://golang.org/pkg/hash/">https://golang.org/pkg/hash/</a>).</p> <p>The real power of interfaces is how you use them to decouple behavior and implementation.</p></pre>kevinw88: <pre><p>Preface: I don&#39;t know Go, I&#39;ve briefly done the tutorial, but interfaces are interfaces no matter the language.</p> <p>An interface is like a contract, it makes promises about what you can do with it. You don&#39;t need to know what&#39;s behind the contract, but only there is a contract.</p> <p>It should make your code more generic and hopefully reusable.</p> <p>Let&#39;s consider a more real-world example other than shapes. Suppose you have code that&#39;s talking to a sql database, and you want to fetch a list of users. There are multiple SQL databases in existence: MySQL, Postgres, SQLite, etc. And let&#39;s say we want to support the three mentioned databases. Without generics, your code would need something like:</p> <pre><code>func (db MySQL) GetUsersMySQL ([] Users) { return db.query(&#34;select * from users&#34;) } func (db Postgres) GetUsersPostgres ([] Users) { return db.query(&#34;select * from users&#34;) } func (db SQLite) GetUsersSQLite ([] Users) { return db.query(&#34;select * from users&#34;) } </code></pre> <p>Then we&#39;ll need something like:</p> <pre><code>if is_using_mysql: GetUsersMySQL(mysqldb) if ... </code></pre> <p>This turns into a lot of code, especially if we want more functions other than GetUsers! Interfaces allow us to say &#34;Hey, I promise to give you a function called <strong>query</strong>, leave it up to us to use the right thing!&#34;. Then your code becomes:</p> <pre><code>func (db SomeDb) GetUsers ([] Users) { // the contract says query exists, so let&#39;s use it! return db.query(&#34;select * from users&#34;) } users = GetUsers(db) </code></pre> <p>Hooray, shorter code! There are other benefits of interfaces, like making your code more easily testable. E.g. In production you want your database to a MySQL database, but during testing you may want your database to be an in-memory database. As long as it implements <strong>query</strong>, it will work!</p></pre>natdm: <pre><p>This is really helpful too, seeing it in a practical sense. </p></pre>kromem: <pre><p>To really get interfaces, I suggest writing a bit of code using TDD.</p> <p>You&#39;ll find that you want to stub out some of your other objects/methods, but won&#39;t know how. </p> <p>Then you&#39;ll remember about interfaces, realize that if you use an interface in your code instead of programming using instances of your objects, that inserting a stub version of your objects becomes super easy. </p> <p>Then you&#39;ll start seeing other ways that use of a more generalized interface instead of actual objects works better. </p> <p>And then you&#39;ll also end up being a better programmer, as you&#39;ll unknowingly have realized some higher level design concepts and why they work well. </p> <p>TL;DR: Try TDD</p></pre>maruwan: <pre><p>Use the Source, Luke.</p> <p>Take any example interface (eg Writer) and look at all the implementations and consumers you can find.</p></pre>lbrtdj: <pre><p>Go has multiple definition for an interface, one is the interface that you speak of: the interface in OOP sense, and the other is the <code>interface{}</code> type. I will talk about the interface in OOP sense.</p> <p>Interface in OOP isn&#39;t strictly Go, and is a common pattern in other OOP programming languages especially like Java/C#. The point of interface is to establish a common functionality without dealing with implementation details. This is to allow extensions, or &#34;plugins&#34;, while keeping the existing code unchanged.</p> <p>Given an interface with a function <code>Area()</code>, you can extend this interface to many different shapes, each with their own ways of calculating the area. Yes, you still have to define the function for each different shape. It&#39;s useful not to the shapes, but to the one that invokes <code>Area()</code>.</p> <p>A real life analogy would be a mailbox is an interface to send mails to. You don&#39;t have to care who&#39;s picking up the mail, or where the actual post office is, or how the mail is delivered. You don&#39;t care about the implementation details, but you want to send mail. Mailbox is the interface between you and the post office system, so you can send some mails, regardless of who picks them up, and who gets to deliver them.</p> <p>Likewise, the one that invokes <code>Area()</code> just want to get the area of whatever it&#39;s using. It doesn&#39;t care if it&#39;s a square, triangle, or circle. It just needs the area.</p></pre>YEPHENAS: <pre><blockquote> <p>Go has multiple definition for an interface, one is the interface that you speak of: the interface in OOP sense, and the other is the interface{} type.</p> </blockquote> <p>They are not different definitions for an interface. &#34;interface{}&#34; is not a special type. It&#39;s just an interface with no methods:</p> <p>var a interface{ Foo(); Bar() } // variable of interface type with two methods</p> <p>var b interface{ Foo() } // variable of interface type with one method</p> <p>var c interface{} // variable of interface type with no method</p></pre>dmikalova: <pre><p>It lets you say &#34;anything can do or use this, as long as they satisfy the interface.&#34; Which means anyone can extend your interface as they need it, but still be compatible with any other library that implements the same interface.</p> <p>It really shines with the http package and frameworks built on top of it - they&#39;re all interoperable so you can compose them and just use the parts that you need. You can make more and stick them in wherever.</p></pre>blueblank: <pre><p>Interfaces are behavior. You can create any number of data aggregations that behave in a way to fit an interface.</p></pre>dvirsky: <pre><p>Someone mentioned writers and readers which are a good example. </p> <p>But maybe try this thought exercise: let&#39;s say you want to write some sort of &#34;social reader&#34; that can read RSS, Atom, and maybe twitter feeds. It reads them all into your own internal &#34;item&#34; object and aggregates them</p> <p>So basically you want to have a list of &#34;item fetcher&#34;s that can be added to a user&#39;s reader. But you want to treat them the same, because basically they all accept some parameters, do their thing internally and produce &#34;items&#34;. something like: </p> <pre><code>type Fetcher interface { Fetch() ([]Item, error) } </code></pre> <p>Now you create 3 implementations that vary on how they work internally and how they are created, but from the point of view of your reader can be treated the same. </p> <p>BTW: In dynamic languages that might not be an issue because of &#34;duck typing&#34; - if walks like a duck it&#39;s a duck, so if a python or js class has a &#34;fetch()&#34; method it&#39;s a fetcher. But in a statically typed language like Go you need a way to express this. </p> <p>I hope this example makes sense. </p></pre>dhdfdh: <pre><blockquote> <p>Most of the examples I&#39;ve seen show, &#34;This is how Java would do the same thing..&#34; Unfortunately, I didn&#39;t get very deep in to Java, so that&#39;s no help to me.</p> </blockquote> <p>OT - I can&#39;t complain enough about articles that give examples and use another language as a reference as if I know anything about that other language. </p> <p>I grew up as an electronic engineer. Assembly was my forte and I was drug, kicking and screaming, into C. I fell into web development, as a favor to someone else, and now I own my own company. So now I&#39;m a programmer but I don&#39;t have experience with, for example, Java or Haskell or etc. </p> <p>Yeah, I&#39;m a pretty good programmer in the languages I know but I&#39;m bound by those cause I don&#39;t have time to study the rest. So quit doing that!</p> <p>There&#39;s something wrong if you can&#39;t explain it in the language I&#39;m trying to learn.</p></pre>natdm: <pre><p>Glad someone with significantly more experience has the same issues at times. There&#39;s some languages I can look at the code and &#39;port&#39; it over - but they&#39;re really just the high level languages at this point in time. </p></pre>divoxx: <pre><p>Think of it as a way of creating a level of indirection between the type system and your concrete implementations, allowing you to create a type that represents any concrete implementation as long as it has the same methods.</p> <p>In the dynamic world, it&#39;s usually done by duck typing. For example, in a dynamic language if you have a function that takes an object as argument, you can call a method on that object inside the method and as long as it implements it, it will work.</p> <p>On the static typing world, type safety is one of the most important things. Since your function now can only take an object of a specific type, without interfaces you wouldn&#39;t have a way to pass &#34;any object that implements method M&#34; into that function.</p> <p>This is a over simplification but I hope it sheds some light.</p></pre>netscape101: <pre><p>Read this: <a href="https://www.golang-book.com/books/intro/9" rel="nofollow">https://www.golang-book.com/books/intro/9</a></p></pre>

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

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