Preferred Golang alternatives to traditional object-oriented patterns?

xuanbao · · 473 次点击    
这是一个分享于 的资源,其中的信息可能已经有所发展或是发生改变。
<p>A lot of programming teaching material these days assumes that you are using a traditional object-oriented language (example: Java) with traditional object-oriented patterns (example: inheritance) to structure and model your program. In the case of Go, it is a different language with different building blocks. Then, what are the preferred ways to translate or replace some of the more common patterns in object-oriented land?</p> <p>Here are some examples of things I have wanted to do in the past, along with some commentaries on how I have done them using object-oriented constructs and patterns. What would be some good alternatives in Go land? What would be idiomatic Go solutions to the same problems, even if they might not necessarily work the same way conceptually?</p> <ol> <li>Domain models D1 and D2 should share some common base functionality, which they each build upon. That is, the code that should differ between the two depends on the code in common. In OOP, D1 and D2 would be derived classes which both inherit from some base class B, and B would contain methods implementing the common functionality. In Go, the best I could think of was that D1 and D2 would be structs implementing some interface I, and the shared functionality lives as helper functions taking an argument of type I in some module H. This works, but it feels a bit strange that now the domain models are not some singular construct but rather live as separate pieces in separate places.</li> <li>Some domain logic should be written once and can be applied to domain model D1 or domain model D2, relying on only the commonalities between D1 and D2. That is, the code in common depends on the code that differs, somewhat of a reversal of (1). In OOP, the shared logic could live in a mixin M, and both D1 and D2 would mix in M. In Go, the best I could think of was that D1 and D2 would be structs implementing some interface I. Then, the shared logic could be a set of functions that took in a parameter of type I. Or, it could be a wrapper struct containing a member of type I. Either way, I feel like I&#39;m hitting the same strangeness of having to separate things that conceptually belong together, similarly to (1).</li> <li>Some code you do control needs to call into and be called from code you don&#39;t control. For example, a framework might provide some integration points where you can supply your own code, but your own code here can also call into the framework&#39;s helpers. In OOP, this could live together in a (possibly abstract) base class, where the base class supplies well-known methods as integration points and other methods as helpers. In Go, the best I could think of was for the framework code to supply an interface, which your code implements, and everything else lives as module-level helper functions.</li> </ol> <p>These are things I have encountered in the past, primarily while writing object-oriented programs, although I&#39;ve tried my best to frame them outside of object-oriented terms and concepts. If any Gophers have any advice on how to re-frame the same ideas in Go terms, that would be very helpful.</p> <p>Any help would be appreciated. Thanks in advance.</p> <hr/>**评论:**<br/><br/>__crackers__: <pre><ol> <li>Composition instead of inheritance using <a href="https://golang.org/doc/effective_go.html#embedding">embedded structs</a>.</li> <li>What you said. D1 and D2 should probably implement interfaces.</li> <li>Again, what you said. Interfaces are probably your best bet.</li> </ol> <blockquote> <p>I feel like I&#39;m hitting the same strangeness of having to separate things that conceptually belong together</p> </blockquote> <p>I dare say the perceived strangeness results from your equating belonging together conceptually with belonging in the same object (i.e. you&#39;re thinking in OOP terms).</p> <p>Do you have any concrete examples that we could help you Go-ify?</p></pre>goomba_gibbon: <pre><p>I completely agree about a concrete example here, even if it&#39;s quite abstract. It would really help me to understand points 2 and 3.</p></pre>mingp: <pre><p>Please see my other reply.</p></pre>mingp: <pre><p>Please see my other reply.</p></pre>Redundancy_: <pre><p>I like Uncle Bob&#39;s SOLID principles (<a href="http://butunclebob.com/ArticleS.UncleBob.PrinciplesOfOod" rel="nofollow">http://butunclebob.com/ArticleS.UncleBob.PrinciplesOfOod</a>) which are fairly well explained in his videos, but you can find other explanations of varying quality.</p> <p><a href="https://code.tutsplus.com/tutorials/solid-part-3-liskov-substitution-interface-segregation-principles--net-36710" rel="nofollow">https://code.tutsplus.com/tutorials/solid-part-3-liskov-substitution-interface-segregation-principles--net-36710</a> is semi interesting to read and think about with a Go view on objects and interfaces.</p></pre>mingp: <pre><p>To be honest, I was confused by this response, because I typically associate SOLID with traditional object-oriented design. In particular, while SOLID itself does not talk about inheritance, I have always seen it demonstrated with such.</p></pre>Redundancy_: <pre><p>Interface segregation says that the interface should belong to the client. This is harder in a traditional OO language that implements interfaces by having to explicitly extend a class with them, because the implementer must now import from the client. Golang&#39;s implicit interfaces help with this, allowing clients to create small interfaces that they need with far less confusion or friction.</p></pre>dinkumator: <pre><ol> <li>In this case, you create a struct B and embed it into D1 and D2.</li> <li>Your first thought here is best, D1 and D2 have some shared interface that they implement. Think a file vs a network connection - both can stream bytes.</li> <li>The framework should provide a well-defined interface and accept it. It doesn&#39;t necessarily need to be module-level helpers either, it can be anywhere. (e.g. gzip doesn&#39;t care where the bytes are going, disk or the network, it just wants to stream compressed bytes)</li> </ol> <p>To really grok the concepts, I&#39;d recommend looking though the stdlib for all the packages implementing <code>io.Reader</code> or <code>io.Writer</code> - check out things like <code>*os.File</code>, <code>*net.Conn</code>, packages <code>log</code> and <code>gzip</code>, etc.</p> <p>You can open a file on disk, extract it through gzip, and copy it directly to a network connection (using <code>io.Copy</code>) simply because all of these implement or accept <code>io.Reader</code> and `io.Writer. Similarly you can make a gzip&#39;ed log file with only a few lines too. It doesn&#39;t matter that the classes are different semantic types because the all use interfaces, not inheritance.</p></pre>kaeshiwaza: <pre><p>composition and first-class functions are my friends when i want to rewrite and object-oriented design.</p></pre>mingp: <pre><p>For those who asked, here are some more concrete examples of each, phrased in more traditional object-oriented terms.</p> <p><strong>Domain models with common base functionality:</strong> A web framework implements a user management module. Users might be anonymous (not logged in) or authenticated (logged in). The code contains a <code>BaseUser</code> abstract base class, with most login-agnostic user functionality, which is then extended by <code>AnonymousUser</code> and <code>AuthenticatedUser</code>. (AFAIK, this is how Django and possibly other OOP web backend frameworks actually work.)</p> <p><strong>Common functionality mixing in:</strong> You have a conceptually comparable (strictly ordered) domain model. You implement a <code>compareTo</code> or <code>&lt;=&gt;</code> method that returns <code>-1</code>, <code>0</code>, or <code>1</code> for the actual comparison operation. Then you mix in some sort of <code>ComparableHelpers</code> mixin, and it implements all the other comparison methods (such as <code>&lt;</code>, <code>&lt;=</code>, etc.) for you. (AFAIK, this is how Ruby does its <code>Comparable</code>.)</p> <p><strong>Framework base class with helpers:</strong> A framework for writing IRC bots supplies an abstract base class <code>BaseIrcBot</code>. It contains a number of integration points for user-defined bot implementations in the form of abstract callback methods, such as <code>onUserJoinedChannel</code> and <code>onReceivedChannelMessage</code>. Additionally, it contains a number of helper methods for functionality that bots can use, such as <code>sendMessageToChannel</code> and <code>sendMessageToUser</code>. Framework users are expected to extend <code>BaseIrcBot</code>, filling in the appropriate callback methods, possible using some of the helper methods in the implementation bodies. (AFAIK, this is now most OOP IRC frameworks actually work.)</p></pre>shovelpost: <pre><p>Most of these patterns you mention, that are common in other languages, can introduce strong coupling. We prefer to avoid that in Go. In fact, Go has been designed in a way to make some of these patterns impossible even. Instead, Go is about composition. I can recommend <a href="https://www.youtube.com/watch?v=zzAdEt3xZ1M" rel="nofollow">this talk</a>.</p></pre>Emacs24: <pre><p>Well, inheritance proved to be anti-pattern or nearly so in practice, and Go doesn&#39;t have it for reason. Even in Java it is recommended to prefer composition over inheritance. In Go you only have a right choice :)</p></pre>

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

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