Please share your thoughts on interface definitions including field values.

polaris · · 428 次点击    
这是一个分享于 的资源,其中的信息可能已经有所发展或是发生改变。
<p>Quite often I want to define an interface that abstracts a set of fields a struct might have.</p> <p>For example, if making a video game, I&#39;d love to have an interface that is satisfied by any struct with a certain field like:</p> <p><code>type Damager interface { Health int }</code></p> <p>Then I could abstract the concept of dealing damage to any unit with health. </p> <p>As far as I know, the current options are:</p> <p><code>type Damager interface { Damage(int) }</code> <code>type Damager interface { SetHealth(int); GetHealth(int) }</code></p> <p>However, that leads to boilerplate getters and setters. Another option that I think is almost very clean is to have a Health struct with those methods defined:</p> <pre><code>type Health struct { Damage(int) } type Tower struct { Health } </code></pre> <p>However, I still need the Tower struct to expose it&#39;s Health field via a method if I want to use interfaces to abstract it away. If I could just have a function accept ANY struct with a Health field, I think it would allow me to be more abstract without paying the boilerplate penalty. Other places where this pops up are: Data Models all having an ID field; Game Objects have a Position field; Catalog Items having a Price field...</p> <p>Does anyone have any thoughts or inputs on this? I&#39;d love to hear alternative solutions to this sort of problem, reasons why my solution couldn&#39;t work, etc. I know reflect could let me inspect the struct to find the Health struct inside, but that loses lots of type-safety.</p> <p>Thanks.</p> <hr/>**评论:**<br/><br/>Sythe2o0: <pre><p>For this specific example, this is how I&#39;ve done it:</p> <p><a href="https://play.golang.org/p/TagRhIVXwI">https://play.golang.org/p/TagRhIVXwI</a></p> <p>Then all entities that want to be damaged have the Healthy struct as a part of their struct and are acted on as Damageables for these functions. Then these basic functions can be overwritten if a struct wants to have different behavior, and they can call </p> <pre><code>mystruct.Healthy.Hurt(damage) </code></pre> <p>Or etc. within their own Hurt function to have access to the original behavior.</p> <p>Another pattern I&#39;ve used for this, if I really just want access to a bunch of fields, is this:</p> <p><a href="https://play.golang.org/p/XQGulRVpAO">https://play.golang.org/p/XQGulRVpAO</a></p></pre>djherbis: <pre><p>I know its just an example, but your hurt method races with the goroutine it creates. Maybe just store the time its invulnerable until on the struct, and check current time against that?</p></pre>Sythe2o0: <pre><p>That&#39;s a better alternative, thanks for the suggestion! I was actually just thinking about other ways of doing the invulnerability timer.</p></pre>gogroob: <pre><p>Instead of creating methods like <code>SetHealth</code>, name the methods <code>TakeDamage(int)</code> and <code>Heal(int)</code> which would subtract/add the health of the object. </p> <p><a href="http://www.yegor256.com/2014/09/16/getters-and-setters-are-evil.html">http://www.yegor256.com/2014/09/16/getters-and-setters-are-evil.html</a> </p></pre>RwKroon: <pre><p>I think it depends on the complexity in your domain. If damage is always straight forward then you could expose the actual field as you would only modify a primitive datatype. However if you consider some sort of shield that absorbs x% of the damage you get a calculation that should be internal to the unit taking damage.</p></pre>danredux: <pre><p>In the case of complexity around Health logic, that&#39;s precisely why I would prefer to have that complexity on the Health struct itself and just embed it, or in another function entirely. If Health included all the fields and methods for calculating damage done, then any unit with a Health field could take damage accurately.</p> <p>In other words, I&#39;d like to be able to properly mix-in structs to compose larger structs, but I feel the only thing missing is access to the embedded struct itself without exposing it via a method. For example if I define, <code>type Tower struct { Position; Mesh }</code>, it would be nice if the Rendering system could accept any struct with a Mesh and Position, but a DistanceBetween function could accept any two structs with Position fields.</p></pre>alayton: <pre><p>Why not have both an interface and a struct that fulfills the interface? The interface gives you the ability to pass your larger structs around, and the struct lets you implement the functionality (and boilerplate) in one place.</p> <p>So you&#39;d end up with something like.. </p> <pre><code>type Damager interface { Damage(int); SetHealth(int); GetHealth(); } type Health struct { Health int; Damage(int) {}; SetHealth(int) {}; GetHealth() {} } type Tower struct { Health } </code></pre></pre>danredux: <pre><p>This solution is pretty much what I&#39;ve been using when this problem pops up. I suppose the missing ingredient is that I want Tower to have a Health field directly. If interfaces included fields, I could work with third-party structs depending on their fields.</p> <p>Go interfaces are duck-typed. By forcing the Tower struct to implement the interface via methods, it no longer feels like they&#39;re separated by that duck-typing. If someone looked at the above solution, they&#39;d wonder why I wrote such a round-about design.</p></pre>Sythe2o0: <pre><p>You can directly modify the tower&#39;s health field, if you have an instance of tower instead of an interface, just by naming the struct containing the int Health something different</p></pre>danredux: <pre><p>Yes, and having a []*Tower would be ideal. However, other units have Health fields, so a []Healther slice would be even nicer.</p></pre>natefinch: <pre><p>The nice thing about Damage(int) is that it makes your types easily composable, e.g.</p> <pre><code>type Damageable interface { Damage(int) } type DamageMultiple struct { d Damageable factor float64 } func (d DamageShield) Damage(n int) { d.d.Damage(int(n*d.factor)) } </code></pre> <p>So you can easily layer on shields that do percentage changes to damage, or subtract flat numbers from damage (or add damage), etc. </p> <p>It also means you can make more interesting things that can be damaged. Like if you have a large vehicle that can take damage in multiple areas, you can figure out where each hit goes internal to the vehicle&#39;s damage function.</p></pre>epiris: <pre><p>This is a bit farther from a direct answer but in game design I like to have Attributes as a general representation of a Entities persistent mutable values like health, mana, strength, gold, etc. So for Go I would probably have a </p> <pre><code>type Attributer interface Set Sub Div Mul, anything else convenient </code></pre> <p>Then I would implement it with a type attribute *int. Then I would create my various actors types define attributes, like a tower</p> <pre><code>Health() Attribute Cost() Attribute </code></pre> <p>Then tower can easily implement Attribute with a new(attribute) in his init. Not 100% sure I would use a interface to start would probably let it prove it was needed first. I would create a struct to group common sets of attributes and embed it for concrete actors. Just food for thought have fun man.</p></pre>weberc2: <pre><p>I don&#39;t think there&#39;s anything to abstract over here. The thing all of your &#34;Healthers&#34; have in common is a single mutable int attribute, so just use an int pointer: <a href="https://play.golang.org/p/1md-Dtsl6v" rel="nofollow">https://play.golang.org/p/1md-Dtsl6v</a></p> <pre><code>func halve(healthPtrs []*int) { for _, ptr := range healthPtrs { *ptr = *ptr / 2 } } func main() { foo := Foo{Health: 10} bar := Bar{Health: 16} fmt.Println(&#34;foo:&#34;, foo) fmt.Println(&#34;bar:&#34;, bar) halve([]*int{&amp;foo.Health, &amp;bar.Health}) fmt.Println(&#34;foo:&#34;, foo) fmt.Println(&#34;bar:&#34;, bar) } </code></pre></pre>

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

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