Edwin Yip 154 Posted March 28, 2019 (edited) I'm not sure how many of you are like me, use implementation (class) inheritance (not interface inheritance, this one is OK) without too much thinking, with the intention of primarily for code reuse. Inheritance for extension is Ok, but for code reuse? Maybe not that good. Although I have achieved code reuse with class inheritance, but from time to time I feel unwanted class coupling were introduced too. It doesn't sound right and today I have spend many hours reading through aquite amount of discussions through stackoverflow, StackExchance, wikipedia, and so on, many articles seem to prove my feeling, for example: https://codeburst.io/inheritance-is-evil-stop-using-it-6c4f1caf5117 (especially this one) https://www.artima.com/lejava/articles/designprinciples4.html https://softwareengineering.stackexchange.com/a/65180/37358 Update 1: Let me quote from StackExchange user Stephen Bailey which is really well said: Quote Inheritance is primarily a polymorphic tool, but some people, much to their later peril, attempt to use it as a way of reusing/sharing code. The rationale being "well if I inherit then I get all the methods for free", but ignoring the fact that these two classes potentially have no polymorphic relationship. So why favour composition over inheritance - well simply because more often than not the relationship between classes is not a polymorphic one. It exists simply to help remind people to not knee jerk respond by inheriting. My conclusion === If it's only about code resue, most of the case we should consider use composition instead of implementation inheritance, like assembling a car with various more fine-grained, more manageable components. What do you think? Edited March 29, 2019 by edwinyzh 1 Share this post Link to post
David Schwartz 426 Posted March 28, 2019 (edited) I suppose there's a huge gap between theory and practice. If you look at the biggest, most mature libraries around, they're all flat. Any inheritance is pretty much within their own frameworks. DOS and Windows started out with APIs as software interrupts, then TSRs, then DLLs. The introduction of C++ only offered lightweight wrappers for most of their smallest objects, and didn't go very far in terms of building a hierarchy of anything. Several GUI libs were introduced, then the VCL came along that was rooted in a TObject, which at the time was seen as "revolutionary" and even somewhat controversial. Indeed, even TurboPascal adherents argued it created confusion with their 'object' type, although it was part of a library rather than the language. Most Delphi libs now derive from some fairly low-level classes, but all are at least TObjects. However, trying to build object-oriented libs around flat DLLs is challenging, to say the least. I've tried it with a couple of Google's APIs and it's a huge challenge with a strongly-typed language like Delphi; maybe a lot less for javascript or more loosely-typed languages. I find I'm extremely hesitant to derive specialized classes from things in 3rd-party libs, unless there's absolutely no question that they won't be useful anywhere else. That's often the case for GUI objects, but not for non-visible functional things. In the latter case, Interfaces are more helpful because you can replace the logic without having to rewrite anything (assuming the model your Interface defines is properly designed, which isn't always the case). Otherwise, composition is the way to go. (Actually, sometimes I'll encapsulate the lib inside of a class to provide a layer of isolation between the lib and code that uses it. This is a mix between composition and inheritance based on interfaces.) Edited March 28, 2019 by David Schwartz Share this post Link to post
Edwin Yip 154 Posted March 29, 2019 Thanks for sharing your thoughts. I think inheriting from TObject, TPersistent, TInterfacedObject, and so on, are ok and necessary, because in that case the inheritance is about polymorphism, not only code reuse. So it's another case I think. Share this post Link to post
Dalija Prasnikar 1396 Posted March 29, 2019 It always depends on particular situation. Both composition and inheritance have their place. Sometimes you can use either, sometimes there is a clear distinction. Some tips that can help you decide. IS-A - inheritance - Sword IS-A Weapon so Sword should inherit Weapon HAS-A - composition or aggregation - Unit HAS-A Weapon so Unit can be composed with Weapon object With simple classes that satisfy IS-A condition plain inheritance will do. With more complex classes, you may want to use composition or delegate some behavior to other classes, especially if that behavior can be reused independently. Delegation - delegate behavior to another class when it is not an objects responsibility to implement behavior on its own Composition - when object is gone the composed objects are also gone - when Pizza is gone so are the ingredients Aggregation - class is used as a part of another class, but still exists outside that other class - when Unit dies Weapon still exists 8 Share this post Link to post
Edwin Yip 154 Posted March 29, 2019 @Dalija Prasnikar Thanks for sharing. Correct, it depends on the specific situation, I agree with you, except that I somehow think the 3 things you mentioned - delegation, composition and aggregation are the same thing. Share this post Link to post
Edwin Yip 154 Posted March 29, 2019 @Dalija Prasnikar I'd like to add two things: I believe you are talking about implementation inheritance but not interface inheritance. The common mistake to make when using implementation inheritance is creating God Object. Share this post Link to post
Anders Melander 1784 Posted March 29, 2019 @Dalija Prasnikar I agree with your characterization but in some cases I find that composition is better suited than inheritance even though inheritance would be more natural. For example if I were to create a list of TSomething it would be tempting to use TList<T> as a base class, Problem is that it might give me too much. If I only need an Add method, a Count and an array property, then composition is probably better (I guess one could call it inheritance through composition). I've seen too many examples of inheritance (not just from TList) where most of the inherited methods doesn't make any sense and would break the application if used. 1 Share this post Link to post
Dalija Prasnikar 1396 Posted March 29, 2019 45 minutes ago, edwinyzh said: @Dalija Prasnikar Thanks for sharing. Correct, it depends on the specific situation, I agree with you, except that I somehow think the 3 things you mentioned - delegation, composition and aggregation are the same thing. Not exactly... there are slight differences in behavior. Not that this part matters when you are choosing between inheritance and composition (as general term) Share this post Link to post
Dalija Prasnikar 1396 Posted March 29, 2019 35 minutes ago, edwinyzh said: @Dalija Prasnikar I'd like to add two things: I believe you are talking about implementation inheritance but not interface inheritance. The common mistake to make when using implementation inheritance is creating God Object. Interfaces don't have implementation, so if you want to reuse code you either have to use class inheritance or you must use composition. IS-A and HAS-A rule can help you determine whether inheritance makes sense in a first place. If IS-A rule is satisfied, then you have to see whether in that particular case you should use class inheritance or composition. When it comes to creating God objects, they break other OO principles. Composition is preferred - problem with that approach and your original question "Prefer composition over inheritance for code reuse?" is that people tend to interpret it like NEVER use inheritance. And then they go down the different rabbit hole. 3 Share this post Link to post
Dalija Prasnikar 1396 Posted March 29, 2019 30 minutes ago, Anders Melander said: @Dalija Prasnikar I agree with your characterization but in some cases I find that composition is better suited than inheritance even though inheritance would be more natural. For example if I were to create a list of TSomething it would be tempting to use TList<T> as a base class, Problem is that it might give me too much. If I only need an Add method, a Count and an array property, then composition is probably better (I guess one could call it inheritance through composition). I've seen too many examples of inheritance (not just from TList) where most of the inherited methods doesn't make any sense and would break the application if used. That is why there are no clean and absolute rules. You have to decide what makes sense in each particular case. Sometimes creating wrapper classes that will expose only valid functionality makes sense, sometimes list is just a list. 2 Share this post Link to post
Edwin Yip 154 Posted March 29, 2019 33 minutes ago, Anders Melander said: @Dalija Prasnikar I agree with your characterization but in some cases I find that composition is better suited than inheritance even though inheritance would be more natural. For example if I were to create a list of TSomething it would be tempting to use TList<T> as a base class, Problem is that it might give me too much. If I only need an Add method, a Count and an array property, then composition is probably better (I guess one could call it inheritance through composition). I've seen too many examples of inheritance (not just from TList) where most of the inherited methods doesn't make any sense and would break the application if used. That's the mistake people often make, otherwise there won't be the God Object wikipedia page :) Share this post Link to post
Edwin Yip 154 Posted March 29, 2019 19 minutes ago, Dalija Prasnikar said: Composition is preferred - problem with that approach and your original question "Prefer composition over inheritance for code reuse?" is that people tend to interpret it like NEVER use inheritance. And then they go down the different rabbit hole. You miss the 'code reuse' part of the original question. In other words, if your purpose is code reuse, think twice before using inheritance, in most cases, maybe composition is more suitable, this may sound extreme, but just as the first linked article said, I tend to try use as less inheritance as possible. And that's the question I want to discuss with your guys here. I didn't mean never use inheritance - when your purpose is polymophism, you need inheritance. Do you know the circle-ellipse problem? That's the problem of inheritance. Share this post Link to post
Dalija Prasnikar 1396 Posted March 29, 2019 15 minutes ago, edwinyzh said: You miss the 'code reuse' part of the original question. No I didn't miss it. But code reuse is separate matter. It is not a primary drive. Code reuse plays significant part in OOP design - you don't want to unnecessarily repeat code, but you also don't want to make your choices solely based on code reuse. Whatever you do, you always must look at the problem from many different aspects and all basic OOP principles. If you take too narrow approach, you will most likely make wrong choice. Sometimes, answer to inheritance vs composition for code reuse is neither, because doing one or the other can result in monstrosity. Sometimes, repeating two lines of code and keeping things simple is more viable approach. So back to your question and original article - "If it's only about code resue" Answer depends on how you interpret the question... it is never ONLY about code reuse... but if reusing some lines of code IS the only reason to use inheritance, then you are probably doing that part wrong... at the same time composition in such case can also be wrong choice. Depends what is the purpose of that code and its function. If you cannot clearly define its responsibility then you are bound to go wrong with composition, too. 1 Share this post Link to post
Dalija Prasnikar 1396 Posted March 29, 2019 51 minutes ago, edwinyzh said: Do you know the circle-ellipse problem? That's the problem of inheritance. It is problem with bad inheritance. Like I previously said, if you take narrow approach and only focus on Circle IS specialized case of Ellipse, you are bound to make a mistake. Even more so, if you lock particular behavior in base class that cannot be universally applied to all sub-classes. 2 Share this post Link to post
Edwin Yip 154 Posted March 29, 2019 Let me reiterate it - need not to say, both inheritance and composition have their own application situations, there is no doubt about it. The point is, to simply put, always consider composition first before you inheriting a class, if both can do the job, prefer composition over inheritance. That's all about the point. Share this post Link to post
Stefan Glienke 2006 Posted March 29, 2019 There is a bit more to it than simply following a mantra - here is some more food for thought: https://www.thoughtworks.com/insights/blog/composition-vs-inheritance-how-choose 5 Share this post Link to post
Rudy Velthuis 91 Posted March 29, 2019 10 hours ago, edwinyzh said: @Dalija Prasnikar Thanks for sharing. Correct, it depends on the specific situation, I agree with you, except that I somehow think the 3 things you mentioned - delegation, composition and aggregation are the same thing. Not at all. But there are many websites that will explain the differences, so there is no need to repeat that here, except what Dalija already wrote. Share this post Link to post
Rudy Velthuis 91 Posted March 29, 2019 2 hours ago, edwinyzh said: The point is, to simply put, always consider composition first before you inheriting a class, if both can do the job, prefer composition over inheritance. I even think that that rule is too strict. It really depends on the situation, and one should not take preference over the other by some rule like that. 1 Share this post Link to post
David Schwartz 426 Posted March 30, 2019 (edited) In another thread, I was asking about a situation that highlights a very poor design choice by Delphi's DB team (this goes back to D2). They created TDataset, which has a rich set of stuff that's quite adequate for most purposes. But then they created TQuery that IS-A TDataset. Unfortunately, they specialized it to the point where it's impossible to serve as a base class for other types of queries. I'm working with two 3rd-party libs that support Oracle and Postgres. They both have a Query component derived from TDataset instead of TQuery. So while both provide the same "logical" support for SQL queries, they're not quite the same in how they do it. I'm trying to create a single common procedure I can use for both of them, but it's nearly impossible without adding a flag somewhere that says which one you're using, then doing if...then...else to select the correct approach depending which type it is. This is exactly what polymorphism is supposed to address. But because the TQuery subclass is too narrowly defined, it's impossible to do. Instead, you have to inherit from something more abstract, TDataset, which leads to a mess. TQuery can neither be used as a base class, nor re-used for similar purposes. I'm guessing that the original designers could only imagine a huge adapter like BDE being used to handle different database query types, and this general TQuery object would delegate DB-specific activities to that adapter. History has shown that to be a poor decision, and no effort has been made to create a better TQuery-like subclass from TDataset that could serve as a base for other types of query objects. Edited March 30, 2019 by David Schwartz Share this post Link to post
Dalija Prasnikar 1396 Posted March 30, 2019 16 hours ago, edwinyzh said: Let me reiterate it - need not to say, both inheritance and composition have their own application situations, there is no doubt about it. The point is, to simply put, always consider composition first before you inheriting a class, if both can do the job, prefer composition over inheritance. That's all about the point. Err.... No... You started with prefer, now you have come to always... and you are conditioning yourself to use composition without considering inheritance... you should consider both at the same time. There are always trade offs and you have to take those into account. If you start with composition and say, OK composition can solve this problem, I am all done, at some point you will make wrong choice because sometimes inheritance will be better fit. Now, I could be wrong, I don't know what is in your head... The more skill one has.. the easier is to decide... Sometimes you will just take a glimpse on some problem and you will immediately know what is the proper solution... but just because you are able to decide fast, that does not mean that you didn't fully considered all consequences of particular solution. Again, while rules help you have to be careful with the rules. They cannot save you from thinking and using the best tool for the job. I don't know how many times I used inheritance over composition or vice versa, I am not counting... it is irrelevant... even if 90% of the time composition is better, that still leaves you with 10% where it is not. 1 Share this post Link to post
Dalija Prasnikar 1396 Posted March 30, 2019 On 3/28/2019 at 8:14 PM, edwinyzh said: Inheritance for extension is Ok, but for code reuse? Maybe not that good. Although I have achieved code reuse with class inheritance, but from time to time I feel unwanted class coupling were introduced too. Another thing... you are asking confirmation for some design approach that didn't feel right at the end. If it does not feel right, there is probably something wrong and it is quite likely that composition would be better choice. If composition solved your problem, probably it IS the right choice and the next time you encounter similar situation it will probably be right choice again. But, without seeing actual code and knowing the context in which it will be used, it is really impossible for us to say one way or the other. 1 Share this post Link to post
Bill Meyer 337 Posted March 30, 2019 6 hours ago, Dalija Prasnikar said: You started with prefer, now you have come to always... and you are conditioning yourself to use composition without considering inheritance... you should consider both at the same time. There are always trade offs and you have to take those into account. If you start with composition and say, OK composition can solve this problem, I am all done, at some point you will make wrong choice because sometimes inheritance will be better fit. Now, I could be wrong, I don't know what is in your head... (snip) I don't know how many times I used inheritance over composition or vice versa, I am not counting... it is irrelevant... even if 90% of the time composition is better, that still leaves you with 10% where it is not. Rules are made to be broken. (Except this one!) Consider the "Law" of Demeter. which proposes that drilling down through layers is a bad thing. Then consider: MyCaption.Font.Color := clRed; Does it really make sense to have to write: _font := MyCaption.Font; _font.Color := clRed; And if layers of dotted notation makes for bad practice, then what does that make of Fluent coding? Sometimes, inheritance is the best choice, but the deeper the inheritance -- in my view -- the more likely it was a bad choice. Sometimes composition is the best choice, but things become murky when some of the members are best assigned dynamically; then you need also consider whether Property DI is a better choice than Parameter DI. And sometimes, a simple function is just fine. Share this post Link to post
A.M. Hoornweg 144 Posted March 30, 2019 On 3/29/2019 at 10:52 AM, Dalija Prasnikar said: IS-A and HAS-A rule can help you determine whether inheritance makes sense in a first place. If IS-A rule is satisfied, then you have to see whether in that particular case you should use class inheritance or composition. When it comes to creating God objects, they break other OO principles. I think that the problem with inheritance is mainly that the descendant object may expose unwanted methods of the ancestor. For example: A "List" is a sequence of stuff and one can insert, delete and read from any valid position (which also makes sorting possible). Retrieval does not necessarily imply removal. A "Stack" is a list with severe constraints, lacking random access and enforcing LIFO behaviour. Most implementations also enforce that retrieval is removal (as in PUSH/POP). A "Queue" is similar, only it enforces FIFO. The problem is the word "constraint". Inheriting from a class in Delphi can extend it, but AFAIK it isn't possible to *hide* methods of an ancestor class, or is it? It is tempting and dead easy to inherit a tStack or tQueue from a tList, but in that case one would expose unwanted methods like Insert(), Delete() and Sort(). Share this post Link to post
Edwin Yip 154 Posted March 30, 2019 (edited) 7 hours ago, Dalija Prasnikar said: Err.... No... You started with prefer, now you have come to always... and you are conditioning yourself to use composition without considering inheritance... you should consider both at the same time. There are always trade offs and you have to take those into account. If you start with composition and say, OK composition can solve this problem, I am all done, at some point you will make wrong choice because sometimes inheritance will be better fit. Now, I could be wrong, I don't know what is in your head... I don't know how many times I used inheritance over composition or vice versa, I am not counting... it is irrelevant... even if 90% of the time composition is better, that still leaves you with 10% where it is not. Well, maybe there is some misunderstanding here. Actually I mean 'consider', NOT 'use composition without considering inheritance' as you said. If I didn't describe it clearly, any native English speaking members here to correct me :P In other words, one should consider composition first, put composition in the first place, only if it doesn't fit, then try consider inheritance. Why so? That's the conclusion I made after I have read a bunch of articles on the topic of 'Prefer composition over inheritance', some of which I listed in my original post. Please note the word 'prefer', it doesn't mean 'conditioning yourself to use composition without considering inheritance' as you said above. And as A.M. Hoornweg above has pointed out: Quote I think that the problem with inheritance is mainly that the descendant object may expose unwanted methods of the ancestor That's exactly what I meant in my original post: Quote from time to time I feel unwanted class coupling were introduced too. The coupling here is conceptual, not on the compiler level - the descendant class and the client knows too much methods about the base class, and that's where the unclearness begins. Finally, let me quote from the first linked article by Nicolò Pignatelli, he describes the idea of 'composition over inheritance' much better than me: Quote Is there a better alternative to inheritance? Of course there is. It’s about mastering a precise design philosophy that automatically avoids the complexity inheritance brings in. Believe or not, I do not use inheritance in the 99% of my code. And I really mean 99%. How do I achieve that number, you may ask? I apply the following rules to my code: I use only two type of class-level declarations: interfaces and final classes; I inject interfaces in dependent classes’ constructors; I don’t allow any class dependency to be injected other than interfaces; I use a Dependency Injection Container (or an equivalent method, depending on which language I’m coding with) to handle the creation of my instances; If I feel I’m injecting too many dependencies in a class, I rethink my design in terms of class responsibilities and using the interface segregation principle; When required, I split complex behavior in multiple final classes implementing the same interface; I use inheritance only, and I mean only, when it makes sense on a semantical level and only for extension purposes, without any base behavior change; I allow myself to break the previous rules only in the 1% of my code. Being able to apply these rules doesn’t happen over night. You need practice and discipline before you internalize them. Your brain will be busy trying to comply with them, leaving few space for the design of your code. Allow yourself some days to get used to the new coding style, because it’s only when you get more familiar that the magic happens. And this is what you will start seeing in your codebase: clear contracts; using interfaces will force you to think in term of communication between objects; thinking “communication first” will give your brain free resources by splitting its job in two steps: design time and implementation time; our brain doesn’t like to work on two different things at the same time, especially switching continuously between the two; isolated, side effect free code units; injecting interfaces only as dependencies will remove every nasty side effect around the code you are working on; changing an interface implementation won’t affect other concrete classes, since they depend only on the interface abstraction; testability; mocking dependencies is extremely easy when they are interfaces; no more “I forgot to disable the constructor / mock that concrete method” troubles in your System Under Test; low, manageable complexity; as everything is isolated, you won’t need to worry about rippling changes; this dramatically decreases the complexity of your code; low cognitive load; with decreased complexity, your brain will be free to focus on what matters; code fluidity; removing any unnecessary coupling, you will be able to move things around way more easily than before; confidence in yourself; being able to test your code in isolation so well will give you a wonderful sense of confidence in changing it; no more hours of manual clicking around for testing, finger-crossed releases and Friday afternoon hot fixes. Edited March 30, 2019 by edwinyzh Share this post Link to post
Stefan Glienke 2006 Posted March 31, 2019 22 hours ago, edwinyzh said: he describes the idea of 'composition over inheritance' much better than me: The problem is that historically Delphi did not know about interfaces and many of the concepts he describes and even though Delphi now knows about interfaces the RTL/VCL and most of other basic frameworks don't use them. So most of the RTL/VCL code is still inheriting from more or less not really abstract classes in some long inheritance chains (remember those posters that came with older versions of Delphi?) - now try to teach a Delphi developer that only knows this example different... Share this post Link to post