Whats the benefit of composition/encapsulation over inheritance?

blov · · 388 次点击    
这是一个分享于 的资源,其中的信息可能已经有所发展或是发生改变。
<p>After using go for a bit I&#39;m not seeing the obvious benefit of encapsulation. Is it that encapsulation covers the errors of not having multi-inheritance in most OO languages. And if that&#39;s the reason, can one just create a linear subclass list in the OO language if that effect was ever needed?</p> <hr/>**评论:**<br/><br/>gentleman_tech: <pre><p>OK, so this is about interfaces.</p> <p>In an OO language, you can define &#34;class Animal&#34; which has a set of behaviours (methods). You can then inherit from that class and create child classes with slightly different behaviours (&#34;class Dog&#34; and &#34;class Cat&#34;). Code that requires an Animal can be safely given a Dog or a Cat because they&#39;re all Animals. This is what inheritance is useful for in OO programs.</p> <p>In Go, you do this by defining an interface. So you&#39;d define an Animal interface, with a defined set of methods on it. You can then implement these methods in a struct called Dog or a function called Cat. As long as they support all the methods of the Animal interface, they can be passed to any code that expects an Animal interface.</p> <p>The beauty of this is that it removes cruft. The code that wants to call Animal.makeSound() may not care about Animal.climbTree(). So it can specify an interface for its needs (call it SoundMaker) that only has the methods it will call (makeSound in this case). Then anything in your code base can be passed to this code by adding the appropriate methods to make it support this interface. </p> <p>Easiest practical example: http.Handler is anything that has a ServeHTTP method. Anything. You can do this:</p> <pre><code>type StaticHandler string func (sh StaticHandler) ServeHTTP (rw http.ResponseWriter, r *http.Request){ rw.Write([]byte(sh)) } </code></pre> <p>and then use that as a handler in a route:</p> <pre><code>rs := StaticHandler(&#34;this example rocks&#34;) http.Handle(&#34;/&#34;, rs) s := http.Server{} _ = s.ListenAndServe() </code></pre> <p>which is pretty neat, huh?</p> <p>In an OO system, the standard library would define &#34;class Handler&#34;. You&#39;d add a ton of methods and stuff to that (see any Rails class ever for details) because it needs to be useful, right? And then you&#39;d create a child class of that to handle the static string example. But that inheritance also inherits everything included in the Handler class, which is probably going to break because you&#39;ve only got a string to work with. You&#39;d probably need to overwrite a bunch of the default behaviours of Handler, and there&#39;d be a lot of complication and pain. All sorts of edge cases that would get tedious to test for and cater to.</p> <p>If your standard library rocks, then its Handler class would contain one method (ServeHTTP) that you could then inherit from and implement with the static string example. But you&#39;d still have a class, which means instantiating it and all the rest of it. You still can&#39;t just hang a function off your simple string type and boom! automatic Handler object like you can in Go.</p> <p>So... to get back to your question.</p> <p>This is encapsulation, broadly speaking. You ask for the things you need by defining an interface. All that you need is encapsulated in that interface, and your code can work with anything that supports that interface (whether it works <em>well</em> is another question).</p> <p>It solves a lot of problems with OO inheritance because inheritance takes the kitchen sink with it in each generation. It&#39;s not just multiple inheritance. Every child has to deal with <em>all</em> the methods of <em>all</em> its parents, and allow for the fact that great-great-great-grandparent methods might get called on its instance. </p> <p>It does take a bit of time to get used to not thinking in objects. But the power is amazing :)</p></pre>redditbanditking: <pre><p>Composition enables polymorphism in OO speak, not inheritance.</p></pre>: <pre><p>[deleted]</p></pre>-proof: <pre><p>And you can effectively do that with encapsulation and then overriding with the same method names- albeit you can access the overridden one explicitly with the encapsulated struct name prefixed.</p> <p>So yeah. They basically seem like the same thing but this is easier to obtain multi-inheritance.. I.e. encapsulate several objects (and conform to multiple interfaces)</p> <p>Thoughts?</p></pre>redditbanditking: <pre><p>Not sure what the [deleted] comment is about. I think you are confusing inheritance with polymorphism. Encapsulation is just composing behaviors together in a class, or a struct with functions. Encapsulation is independent of inheritance or polymorphism. It&#39;s simply just a class.</p> <p>Inheritance inherits previous encapsulation. It is a form of code reuse. If you have defined a concrete class <code>Person</code>, with all its behaviors, you can inherit another class <code>Child</code>, and add additional behaviors that only applies to a child. But an instance of <code>Child</code> still carries all <code>Person</code> methods and behaviors. Inheritance not only inherits methods but also properties of its parents.</p> <p>Interface composition in Go, or polymorphism, allows you to mix in multiple behaviors together, but the actual behavior is defined by the struct that implements the interface. The <code>Closer</code> interface, for example, defines a <code>Close</code> method, but what the <code>Close</code> method does is defined up to the struct that implements that interface. Unlike inheritance, the struct that implements the interface is also not limited to implementing the <code>Closer</code>, it can implement other interfaces. This is <strong>very</strong> different from multiple inheritance, which is terrible anyway due to the diamond problem.</p></pre>: <pre><p>[deleted]</p></pre>-proof: <pre><p>Sure function composition is different but I&#39;m pretty sure composition and encapulation of objects is the same thing- maybe not in fo terms. I recently read how you can merge objects together with object.assign in JavaScript to mimic composition and to be honest - that&#39;s effectively encapsulation. Function composition is a different beast</p> <p>And I realize that inheritance is different and go lacks it- that&#39;s the point of this thread. Don&#39;t they get you the same thing?</p></pre>jerf: <pre><p>I think you may need to try defining your terms more carefully and possibly re-asking the question. You&#39;ve used a lot of terms in that question that a whole bunch of people will loudly and vigorously declare are very, very well defined with a very, very solid definition... but it will turn out they are using a lot of different solid definitions of those terms. And I&#39;m not sure which of those definitions you are using. Particularly the way you&#39;re using &#34;encapsulation&#34;, which in Go would generally refer to the lower-cased private values and methods you can define on structs and interfaces (and even then the word &#34;encapsulation&#34; doesn&#39;t appear in the Go spec, so I&#39;m extrapolating even to get that far), but I can&#39;t figure out how that could in any way cover &#34;multi-inheritance&#34; (multiple inheritance, I assume). I&#39;m not trying to be critical per se; such criticism that you may pick up from this post is actually directed at programming communities in general for not realizing how often their definitions don&#39;t apply out of their own little community. (And then getting <em>very pissy</em> with people who disagree with their local definitions....)</p></pre>chronokitsune3233: <pre><p>I&#39;ll ignore the issue with your terminology that others have pointed out.</p> <blockquote> <p>Is it that encapsulation covers the errors of not having multi-inheritance in most OO languages.</p> </blockquote> <p>Call me oblivious, but what errors are caused by not having multiple inheritance in an OO language?</p> <blockquote> <p>And if that&#39;s the reason, can one just create a linear subclass list in the OO language if that effect was ever needed?</p> </blockquote> <p>No. You cannot emulate inheritance using composition.</p> <blockquote> <p>Whats the benefit of composition/encapsulation over inheritance?</p> </blockquote> <p>I&#39;m assuming you&#39;re referring to behavioral composition (via interfaces) since object composition just means an object of type T1 contains an object of another type T2 (T1 and T2 are not necessarily different types).</p> <p>In the case of behavioral composition, you might consider that there isn&#39;t a need for tight coupling in all cases. A Penguin isn&#39;t a Bird; it&#39;s a Swimmer and a Walker, just as a Hummingbird is only a Flyer (and maybe a Hopper, but certainly not a Walker). Lions are Walkers too. In other words, both Lions and Penguins could satisfy the Walker interface, meaning they both have a Walk method, though how that might be defined is up to the implementation of those methods for their respective types. Behavioral composition defines what an object can do.</p> <p>In the case of object composition, a Penguin has wings, feet, etc. like all birds. Object composition defines what an object has.</p></pre>Redundancy_: <pre><p>I&#39;ll have a stab at this just to get my own thoughts in order.</p> <p>Firstly, I want to be correct about using terms:</p> <ul> <li>Polymorphism: the provision of a single interface to objects of different types</li> <li>Encapsulation: Preventing direct access to internal data structures</li> <li>Composition: a way to combine simple objects or types into more complex ones</li> </ul> <p>Also useful: <a href="http://www.mit.edu/%7E6.005/fa14/classes/14-inheritance/#reading_14_inheritance__composition" rel="nofollow">http://www.mit.edu/~6.005/fa14/classes/14-inheritance/#reading_14_inheritance__composition</a> (1)</p> <p>Interfaces provide polymorphism - as a piece of code, I can use some thus-far unknown and unwritten thing, as long as it satisfies some constraints that I have about how I&#39;m going to call it and what I expect back.</p> <p>I can open doors - in particular, I can call something that has an Open() function, and I expect back an error in case that doesn&#39;t work, perhaps because it&#39;s blocked, jammed, locked or something else. As the consumer of an interface, I&#39;m the one that really knows what I need satisfied for it to be usable, so it also makes sense for the consumer of an interface to define what it&#39;s able to accept. It can make less sense for a Door to try and define a universal Openable concept for some else, but that&#39;s what happens in a lot of languages.</p> <p>I&#39;m a library, and I like to use Openable things, that&#39;s my interface. Gimme those, and I will open all day long. Unfortunately, what about other libraries that implement Openable things that don&#39;t know about me? If you need an object to extend an interface to implement it, all implementers must know about the consumers, which can be a bit arkward. Similarly, if I as a consumer have to know about all things that can satisfy me, it makes me very difficult to to open for extension.</p> <p>So I would make an argument that Go gets interfaces right - they can live with the one who depends on them, not the one who implements them, and the one that implements them does not need to import the one who depends on them.</p> <p>Since we&#39;ve dealt with interfaces, we don&#39;t need to make the argument that inheritance provides a mechanism for polymorphism. You&#39;ve got one, and a very pure one (literally) in interfaces (regardless if they&#39;re Go or Java-like). Your abstraction can come from there.</p> <p>Inheritance provides a way to reuse code, but it does so in ways that can cause a lot of problems:</p> <ul> <li><a href="http://igstan.ro/posts/2011-09-09-how-inheritance-violates-encapsulation.html" rel="nofollow">http://igstan.ro/posts/2011-09-09-how-inheritance-violates-encapsulation.html</a></li> <li><a href="https://en.wikipedia.org/wiki/Circle-ellipse_problem" rel="nofollow">https://en.wikipedia.org/wiki/Circle-ellipse_problem</a></li> <li>When a subclass overrides superclass methods, it may depend on how the superclass uses its own methods. [1]</li> <li>When a class is subclassed, either it must freeze its implementation forever, or all its subclasses must evolve with its implementation. [1]</li> </ul> <p>Composition however, means that I use an existing implementation without poking into it. I hold on to one of whatever it is, and I let it do the work, perhaps just by delegating calls down to it (which we can potentially do automatically to make things easier in Go). Encapsulation is not violated in this way. Pre- and post- conditions / invariants of the existing object are maintained. </p> <p>Go&#39;s interfaces provide a significant amount of what we need to do polymorphism, keeping well away from the issues of inheritance, and do it the right way around. Composition allows us to do code reuse, without many of the dangers to the Liskov Substitution principle. If we perhaps give up some potential power, we also gain some safety without giving up polymorphism.</p></pre>

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

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