21 ms·
Inheritance versus Composition
- bgschiller 8y agoNice write-up! Coincidentally, I wrote on the same topic, also in python just a few days ago [1] I came to largely the same conclusions, but instead of forwardable, I wrote a new library called superdelegate that is, IMO, more explicit [2] 1: https://brianschiller.com/blog/2019/05/03/two-sorted-lists https://brianschiller.com/blog/2019/05/03/two-sorted-lists 2: https://github.com/bgschiller/superdelegate https://github.com/bgschiller/superdelegate
- seishan 8y agoThat was a great read that was easy to digest. Thanks for writing this up!
- btown 8y agoAlternately, if you want to just delegate everything, you can subclass wrapt.ObjectProxy https://wrapt.readthedocs.io/en/latest/wrappers.html#object-proxy https://wrapt.readthedocs.io/en/latest/wrappers.html#object-... Python is scary sometimes. If it looks like a duck and quacks like a duck and isinstance(duck), it might be an alien masquerading as a duck using metaclasses to accomplish these feats. But with this craziness comes the ability to make a transparent runtime proxy to just about anything in Python, and do so recursively e.g. to track all uses of an object hierarchy.
- bgschiller 8y agoI think I'm confused. If you want to delegate _everything_, why not just actually use inheritance? Or even the object itself. What's an example of when you would want an ObjectProxy? Definitely agree about metaclass magic being a blessing and a curse. That superdelegate library was fun to write and uses a metaclass to make the API pleasant.
- btown 8y agoIf you don't know what you're going to need to wrap (heck, it might be something with methods implemented with native code), but you want to override or intercept a certain subset of calls, then composition/delegation is really the only thing you can do. It's significantly more stable than trying to find the class of an arbitrary object at runtime, create a subclass of it, and try to copy the state of the original object. For instance, I've seen instrumentation libraries monkey-patch database cursors with wrappers around those cursors, so the library can detect usage of the cursor without changing its behavior. Django itself has a LazyObject class, which operates similarly to an ObjectProxy but only evaluates its wrapped initializer when one of its properties is accessed: https://docs.djangoproject.com/en/2.2/_modules/django/utils/functional/ https://docs.djangoproject.com/en/2.2/_modules/django/utils/... And I'm using wrapt in a project (which I hope to open source) where, by recursively wrapping the return value of every __getitem__ and __getattr__ with a properly-bookkept proxy object, you can see what subgraph of a complicated object hierarchy was actually used by a function call (say, the rendering of a Django or Jinja template). Possibilities of such a transformation are endless!
- zmmmmm 8y agoYou don't have to sacrifice type safety, that's a python problem. Other languages (eg: Groovy) accomplish the same thing in a type-safe way (see @Delegate annotation [1]). http://docs.groovy-lang.org/2.4.9/html/gapi/groovy/lang/Delegate.html http://docs.groovy-lang.org/2.4.9/html/gapi/groovy/lang/Dele...
- skocznymroczny 8y agoIn D some of that can be done with a Proxy mixin https://dlang.org/phobos/std_typecons.html#Proxy https://dlang.org/phobos/std_typecons.html#Proxy
- jnwatson 8y agoI like it. I run into this use case all the time.
- kazinator 8y agoIn this issue, proper understanding is muddled by "OOP" languages that have a static type system which uses inheritance as a gatekeeper to polymorphism. So that is to say, if you want to dynamically substitute objects of class D1 and D2 in the same places in the program, these classes have to inherit some base B. Then D1 and D2 objects can work in places that require a B. For this reason, sometimes the B is a dummy that contains no implementation at all. In recognition of such dummy bases, some languages have "interfaces" and "interface inheritance" to formalize the concept. In true OOP, there is no such requirement for D1 and D2 to be a new kind of B. Objects are substitutable into a situation if they simply respond to all the required methods. Inheritance is then used optionally just as a trick for implementation reuse. That is to say, we not only want D1 and D2 to be substitutable, but since they are similar, we would like to have a large fraction of their code in common, not to mention the instance data which that code refers to. The problem is that inheritance is like a software fork. There is this basic implementation of something B, and then these derivations D1 and D2 are like branches: they use the B code, but they replace some of it with their own versions of functions. Those functions initially correctly mimic what their counterparts in B are doing. But if B is subsequently maintained, that can fall apart. B functions can be altered, while their counterparts in D1 and D2 remain the same, not incorporating the new logic. This kind of inheritance situation works best when it's all maintained by one developer, or team, as a cohesive whole. Or else, the base has to be very well designed for inheritance and maintained very carefully. Inheritance has issues that interact with mutability. The circle-ellipse problem goes away if objects are immutable. In the Common Lisp object system there is an analogy there in that numbers of type rational are subclassed by inheritance into ratio and integer. An integer isn't a new kind of rational; it's a restricted kidn of one, just like a circle is a restricted ellipse. Since we cannot mutate an integer (it is what it is), inheritance models things just fine here; it is quite suitable for partitioning a mathematical set. In terms of substitutability, it is perefect: certainly wherever rationals are allowed, we should be able to use an integer or a ratio, just like where ellipses are allowed, we should be able to use a circle. If we ask an integer what its denominator is, we get 1.
- kazinator 8y agoLet's muddy the waters a little: This is the TXR Lisp interactive listener of TXR 215. Quit with :quit or Ctrl-D on empty line. Ctrl-X ? for cheatsheet. 1> (defstruct fixed-list nil a b c (:method car (me) me.a) (:method cdr (me) (list me.b me.c))) #<struct-type fixed-list> 2> (new fixed-list a 1 b 2 c 3) #S(fixed-list a 1 b 2 c 3) 3> (car *2) 1 4> (cdr *2) (2 3) 5> [mapcar square *2] (1 4 9) 6> (cons 0 *2) (0 . #S(fixed-list a 1 b 2 c 3)) 7> [mapcar square (cons 0 *2)] (0 1 4 9) :)
- primaryobjects 8y agoComposition also facilitates utilizing (GOF) design patterns. They can make code much cleaner and reusable when used properly and sparingly.
- macintux 8y agoOne of the most memorable eye-opening moments in my (admittedly fractured) education was when when my software engineering professor listed the 3 fundamental tenets of OO—inheritance, encapsulation, and polymorphism—and then asked us which one was optional. Ever since then I've been on an anti-inheritance kick, although for the last several years it's polymorphed into an anti-OO kick. Anyway, I suspect most readers here have heard the idea that inheritance should be used in limited circumstances (or not at all), but I wonder what proportion of college students get that message today.
- kazinator 8y agoIt was a trick question: two are optional. (I pity the educator if he or she didn't know this.)
- macintux 8y agoIf you eliminate polymorphism and inheritance, is what's left really all that useful?
- kazinator 8y agoThe one we can dispense with is encapsulation. Polymorphism, no; I mean, how can we substitute different kinds of objects into the same situations dynamically without polymorphism. OOP is almost synonymous with polymorphism. But we do not have to bundle code and data representation together; there are other approaches to OOP, like generic functions.
- yourbandsucks 8y agoI mean, you can do pure functions. And those pure functions can operate on structs. And, once you're there, it's probably good hygiene to keep all the functions operating on a given struct in one file. Congrats, you're back to 'encapsulation'! Encapsulation is actually the only important tool in that particular toolkit. We're mortals, we need coping tools to keep programs manageable.
- js2 8y agoThis talk from PyCon 2013 covers much of the same ground: "The End Of Object Inheritance & The Beginning Of A New Modularity" https://www.youtube.com/watch?v=3MNVP9-hglc https://www.youtube.com/watch?v=3MNVP9-hglc
- fao_ 8y agoThis is amusing. Earlier today I was reading through Sonya Keene's book on CLOS, and wondering why OOP had to be hierarchical rather than compositional, and wondered if there had been any work done in this space.
- shmerl 8y agoModern programming languages like Rust avoid classic inheritance: https://doc.rust-lang.org/book/ch17-01-what-is-oo.html https://doc.rust-lang.org/book/ch17-01-what-is-oo.html
- userbinator 8y agoThere are, of course, disadvantages to composition. It requires more code than inheritance, as we saw, Ortiz said. It is often more difficult to read the code using composition than it is to read code using inheritance. I think this is the main disadvantage, at least in languages like C++ and Java; if you want to be dogmatic about the OOP-ism of encapsulation and that stuff, all those "methods that just call another method on the composed sub-object" create lots of bloat that a compiler may or may not be smart enough to inline, but a human certainly would have to deal with --- and that adds overhead when debugging and otherwise maintaining the code. Speaking as someone who really hates such "useless" code, composition (especially sometimes of the nested variety) can get annoying really really quickly ---needing to "thread the call" to the right place through many composed objects just feels more like busywork than accomplishing anything functionally useful. If you inherit, you automatically get the union of the methods (and only when there's name collisions do you need to disambiguate), but inherit too deeply or widely and it gets more difficult to find where the code is. Of course, none of this is relevant at all to the code I normally write using C, where there is no inheritance and everything is composition, and not being an "OOP purist", I'm perfectly happy with BAR_dosomething(&foo.bar);
- alasdair_ 8y ago>needing to "thread the call" to the right place through many composed objects just feels more like busywork than accomplishing anything functionally useful Doing this is a pretty strong code smell. If you have chains of object references that need to know about the internal structure of other objects, perhaps think about the Law of Demeter? https://en.m.wikipedia.org/wiki/Law_of_Demeter https://en.m.wikipedia.org/wiki/Law_of_Demeter
- userbinator 8y agoThat's exactly the problem with being dogmatic about OO --- even the page you linked to mentions it: Although the LoD increases the adaptiveness of a software system, it may result in having to write many wrapper methods to propagate calls to components; in some cases, this can add noticeable time and space overhead Suppose foo() inside class X needs an extra parameter or similar, but that parameter actually needs to come from A which contains an instance of B which contains a C which contains a ... ... which contains the X. Instead of changing only foo() and the b.c....foo() in A, being dogmatic means having to change all the do-nothing wrapper methods on that call chain. If anything, I'd say wrappers are the real code smell. You can probably tell I'm not a fan of OO; I've encountered the aforementioned situation countless times, where the real purposeful change takes a tiny fraction of the time compared to going through all that other do-nothing code and changing it too. The mind-numbing depth of indirection and wasteful bureaucracy is excruciating.
- codr7 8y agoGo makes composition less powerful than it could be by slicing structs, which means a lot of hoop jumping to get template methods (where blanks are filled in downstream). I've been playing around with first class environments [0] lately in g-fu [1], which allow convenient and flexible composition of data and behavior using only what was already there. I have a feeling Lua might be playing similar tricks with its tables, but I lack enough experience to judge. [0] https://github.com/codr7/g-fu/blob/master/v1/lib/eos.gf https://github.com/codr7/g-fu/blob/master/v1/lib/eos.gf [1] https://github.com/codr7/g-fu https://github.com/codr7/g-fu
- akyu 8y ago>slicing structs Can you explain what this means? Still learning Go. Google didn't help.
- codr7 8y agoI'll try :) The issue is that you can't interface methods from pointers to a contained struct, since it has no idea where it is or how it got there. Instead a pointer to the full struct needs to be threaded all the way down to where the overridden function is called. I sort of understand why, but this is a major missing piece that makes it very frustrating to add anything but trivial default implementations to interfaces in Go from my experience.
- chrisweekly 8y agoI'm partial to Eric Elliott's take[1] which highlights the brittle rigidity of _classical_ inheritance in particular (vs eg prototypal inheritance). "OO" is often conflated with class-based inheritance per se. https://link.medium.com/k0YynV2rxW https://link.medium.com/k0YynV2rxW
- yawaramin 8y agoEiffel's take on inheritance might be an answer to its 'perils': https://www.eiffel.com/values/design-by-contract/introduction/ https://www.eiffel.com/values/design-by-contract/introductio... Here's the crucial part: in Eiffel, methods can have preconditions and postconditions, and overridden methods in subclasses must honour those conditions in specific ways: > The principle of subcontracting follows from these observations: a redefined version of r may keep or weaken the precondition; it may keep or strengthen the postcondition. Strengthening the precondition, or weakening the postcondition, would be a case of “dishonest subcontracting” and could lead to disaster. The Eiffel language rules for assertion redefinition support the principle of subcontracting. In a sense, languages which support inheritance might not actually be doing it correctly if they don't support these rules of 'subcontracting'.
- Fannon 8y agoOne article that helped me to understand the OO inheritance problems understand much better is this one: https://www.sicpers.info/2018/03/why-inheritance-never-made-any-sense/ https://www.sicpers.info/2018/03/why-inheritance-never-made-... It argues that there are three different kinds of inheritance and in OO you often mix them up into one hierachy - which may work in some situations, but in some it just can't. Personally, I've experienced this kind of conflict often enough.
- deleted 8y ago[deleted]
- Keshavjois 8y agoFun way of describing inheritance :D "You wanted a banana but what you got was a gorilla holding the banana and the entire jungle." -Joe Armstrong
- kraftman 8y agoInheritance drives me nuts. I'm trying to read through a class and keep it all in my head, then I see a call that isn't defined, so I have to look at the base class. Now I have two classes I'm trying to keep in my head. But oh wait, it inherits from something else. Then I finally find the implementation and ive forgotten the context it was getting invoked in. I'd much rather just have an explicit import providing code reuse.
- hnick 8y agoAn IDE would help with this, at least for a strongly typed language ('go to definition'). For something like Perl, good luck! My other complaint is when the base class is updated independently of the child class. Suddenly your code is breaking because a new field is being set in the original function but not your override. For that reason I tend to inherit to extend only, as much as possible. I prefer to write a wrapper if it's a neater solution.
- crimsonalucard 8y agoDefinitely need an IDE for inheritance and OO heavy projects. It's not an apple and oranges thing, it's required.
- collyw 8y agoA decent IDE will help. I have found this site incredibly good for Django's class based views (which I struggles to understand before using it). https://ccbv.co.uk/ https://ccbv.co.uk/ I wish there was a way to auto generate something like this for all inherited code.
- tabtab 8y agoRe: I'd much rather just have an explicit import providing code reuse. I tend to agree. We need something closer to set theory to manage variations-on-a-theme rather than hierarchies: a buffet where one can pick and choose the parts as needed, and referencing groups of parts using group names to save time and provide abstraction/factoring. It's why I mentioned we need to get some inspiration from relational, which is heavily influenced by set theory.
- fphilipe 8y agoAaron Patterson (aka tenderlove), Ruby and Ruby on Rails contributor, has a good post titled "YAGNI methods are killing me" where he points out the problems when inheriting from builtin classes such as String or Hash. https://tenderlovemaking.com/2014/06/02/yagni-methods-are-killing-me.html https://tenderlovemaking.com/2014/06/02/yagni-methods-are-ki...
- boffinism 8y agoSide note: I always feel pretty weirded out visiting a website called tenderlovemaking.com. It feels gross and creepy and not something I want when I'm doing my professional job in a professional environment. Is it just me, or does anyone else feel like this tenderlove/tenderlovemaking thing is a bit uncomfortable-making and maybe not ok?
- zaphar 8y agoIt's not just you. I also think the domain name is in poor taste.
- mercer 8y agoIt sorta suits the Ruby 'aesthetic', perhaps, but considering Ruby/Rails' current age and image, it makes me think of the Buscemi meme (https://i.kym-cdn.com/entries/icons/original/000/018/666/fellowkids.jpg https://i.kym-cdn.com/entries/icons/original/000/018/666/fel...). I say that with a lot of affection for Ruby though!
- jzoch 8y agoGrpc-Java makes staying away from inheritance incredibly difficult (since they dont present an interface to the service methods you want to call). This makes adapters, decorators, and other useful patterns difficult to employ without the rigamarole of defining the interface yourself and duplicating the delegation.
- kstenerud 8y agoIt would be nice if languages provided mechanisms to support composition without the bureaucracy. Why should we write a ton of boilerplate just to forward a message to a contained object? Why can't we just have: type MyType { member containedObject SomeOtherType forwardTo(containedObject) MethodA, MethodB as SomeOtherName, MethodC method SomeNewMethod() {some implementation} } Then the compiler can optimize the path to call the delegated object directly rather than through a secondary call via this object, since it knows what our intent is.
- perlgeek 8y agoPerl 6 does, with a "handles" trait: class MyType { has SomeOtherType $.containedObject handles ('MethodA', MethodB => 'SomeOtherName', 'MethodC'); } Very useful when you need it. https://docs.perl6.org/language/typesystem#trait_handles https://docs.perl6.org/language/typesystem#trait_handles
- fanf2 8y agoSimilar in perl5 with Moose or Moo package MyClass; use Moo; has member => ( is => ‘rw’, handles => [qw[ methodA methodB ]] );
- gridlockd 8y agoSome languages do specify Mixins, but even multiple inheritance is better than the oversold "composition pattern". The composition pattern is just one of the many ways Java programmers figured out to write more code that does essentially nothing. That's why Java programmers will be in demand forever, whereas more productive programmers will optimize themselves out of a job.
- zmmmmm 8y agoThat's exactly what Groovy provides with the @Delegate annotation [1] [1] http://docs.groovy-lang.org/2.4.9/html/gapi/groovy/lang/Delegate.html http://docs.groovy-lang.org/2.4.9/html/gapi/groovy/lang/Dele...
- unicornmama 8y ago
- thaumaturgy 8y agoI wonder why so many books and articles like this one are written as though the approach they're championing is the only right approach, or always the right approach for every situation. There's never any consideration like, "in the following examples, this might be a good tactic..." Looking at the subject for this post: Inheritance is a good, straightforward approach when you have multiple related classes which share a lot of code but don't interact directly with other unrelated classes: class document { public function title () { } public function get_plain_text () { } } class word_document extends document { public function get_plain_text () { } } In a document management application, you might need to represent lots of different kinds of document formats, each with their own specializations, but also with a lot of common attributes too. These document classes might not need to interact directly with a database or a network socket, so a straightforward and traditional class hierarchy would be the natural choice here. But then let's say you wanted to build an abstract data pipe. A data pipe needs to talk to lots of different things; maybe it's a plain old network socket, maybe it's a database engine, maybe it's an API. Here, you get no advantages from using inheritance, because although each data pipe is trying to mimic the behaviors of any other data pipe, under the hood their code is completely dissimilar. You'd end up having to rewrite so much of the functionality that there'd be nothing really for a base class to actually do. This is where composability and interfaces are the right approach: class phabricator_datapipe implements datapipe { private $_api_client; public function __construct ($api_client) { $this->_api_client = $api_client; } public function search ($parameters) { // Build a search query out of $parameters specific to // this API, then: $this->_api_client->search(...); } } Each datapipe class strictly follows a common interface structure. Dependency injection is used during instantiation to compose the runtime object, so the responsibilities in the datapipe class are limited to translating the application's business needs into the specific structures required by the API, while the API class takes care of authentication and error handling. There is room for both of these approaches in the same application, because they solve entirely different problems. The inheritance of the documents classes solves the problem of dealing with similar-but-different units of data in a maintainable, extensible way. The composable interface of the datapipe classes solves the problem of interacting with completely different external components in similar ways. This likewise is where the post falls over, IMO. It presents an argument for composition, but is too lazy to provide any of the context that really makes composition look like the right answer. Whether a Car object should be modeled as a child of a Vehicle base class, or whether it should be more of a standalone class with other objects injected into it, depends entirely on whether the application just wants to say: Vehicle->GetTimeToDestination(...) or whether the application instead needs to: Vehicle->StartYourEngine(...) (Aside: I think a major failing of posts and books on programming is using contrived, made-up examples, especially when talking about abstractions like object-oriented programming. Those don't help anybody understand the advantages and disadvantages of different approaches. I'm using some examples from applications I've written.)
- ilitirit 8y agoI feel many people are overlooking the most important bit: > He ended with a Spanish question that was evidently made popular in a US advertisement: "¿Por qué no los dos?", which means "Why not both?". Inheritance and composition are both useful tools; developers should understand when to use each.
- zaphar 8y agoWhen should you use inheritance. I can think of one use where it's acceptable and not dangerous. Implementing base class methods that are stateless helpers for sub classes. But I'm not sure that inheritance is the best way to solve it. You would probably want to use interfaces and static methods instead. Basically I literally can't think of a case that isn't better solved by another way. Some languages may not give you those other options, in which case, by all means use inheritance. Just make sure you are careful not to shoot yourself in the foot while doing so.
- barking 8y agoProblems caused by inheritance is not one people maintaining legacy vb6 software have to worry about. But in the day the main reason VB6 was called a toy language was because it wasn't fully OO, it lacked inheritance. And in his 1999 book Francesco Balena showed how this handicap could be worked around, using delegation.
- collyw 8y ago"You wanted a banana but what you got was a gorilla holding the banana and the entire jungle." Aren't mixins a way of avoiding this problem while using inheritance?
- miiiiiike 8y agoSome languages don't support multiple inheritance so mixins aren't an option.
- marcosdumay 8y agoAnd then we get programmers that only know those languages claiming that inheritance if bad because the only language they know only supports an useless simulacra of it.
- eithed 8y agoIsn't it dependancy injection rather than composition? I'd think JS old time "inheritance" where methods are being copied from class to class is composition.
- bmn__ 8y agoarticle with better code, comparing languages and more substance: http://radar.oreilly.com/2014/01/horizontal-reuse-an-alternative-to-inheritance.html http://radar.oreilly.com/2014/01/horizontal-reuse-an-alterna...
- slifin 8y agoJava esque OO is often tauted as a way to reuse code, but incredibly does the opposite by dictating a tree design and then actively disallowing reuse between arbitrary leaves of the tree I think the worse part is this plague has descended into other languages, if you want different behaviour for different things (but common in someway) just accept a function into your function & invoke it, tada more efficient dependency injection
- harimau777 8y agoWhat advantages do proponents of inheritance say that it brings to the table? As far as I can tell, the code reuse aspect can be replaced by composition, substitutability can be replaced by subtyping, and encapsulation can be replaced by immutability and functional purity. On the other hand, the problem that I see with inheritance is that it ties code reuse and subtyping together. I don't see any reason why the two should necessarily be related.
- remote_phone 8y agoThe mental model of inheritance is easy to grasp. I think inheritance has its place as long as things don’t get too crazy. For 90% of projects out there, inheritance is a simple model to understand and can be used just fine. It’s when “architects” get overzealous and try to force everything into a hierarchy where things get bad.
- norswap 8y agoThis is missing the biggest difference between the two, at least according to me. In composition (and its on-steroid cousin, delegation), the component/delegate can't "call back" into the aggregating object. Of course, this additional power can be misused, but it's what should inform the choice between inheritance and composition. Use the least powerful thing that will work — so composition if you don't need to call back into the aggregating object.
- gugagore 8y agoThanks for pointing this out. I can't think offhand of an example to illustrate when you would like this "call back" functionality in a way that you can't anticipate when you design the component object. Do you have a killer-app for inheritance?
- dfinninger 8y agoMaybe a plug-in system? It can dynamically register itself with the parent object so that the plug-in is discovered by the system. All the plug-in author would need to do is inherit from the base plug-in class.
- norswap 8y agoAny time the component needs to call a method or access data from the aggregate. The kind of thing that happens all the time but the pattern is abstract so there's no killer example. e.g. the component manages some kind of list, and the content of this list has to be kept in sync with some field in the aggregate. No example is going to be perfect, because if you have full control over both classes, well then you can always change their interface in a way that it isn't required anymore. One need to consider the reason the thing should be a component in the first place (maybe the pattern is reused in multiple places) and other external and/or historical constraints on the code.
- davidcuddeback 8y ago> In composition (and its on-steroid cousin, delegation), the component/delegate can't "call back" into the aggregating object. It can if you design it to take a pointer to the aggregating object. Pass this or self or whatever keyword your language uses to the component/delegate and then it can call back to the aggregating object through an explicit pointer. This is more explicit than calling through the object's vtable, which makes the code easier to read IMO, e.g., foo.bar() instead of bar().
- jgalt212 8y agoPython, in a sense forces inheritance, because with composition there are too many dots (i.e. objects get too nested)
- gerbilly 8y agoI find that in a lot of critiques of programming methods, the proposed solutions often contain the implicit assumption that the 'code' is a small to medium codebase with a single developer. If a sole developer is the only one working on it, of course any approach can contain the complexity. But, if in the language you are using, the alternative to inheritance is composition via code duplication, then I think that's even worse than inheritance, especially once the code is unleashed on a whole team of maintenance developers. Who says all that duplicated code will be left in its pristine state, and used only for it's intended 'compositional' purpose? People might start hanging special logic in there or whatever... Generally, for projects with large teams like this, the less code the better, because that means there's less code for them to screw up.¹ If that means using inheritance for reuse a bit even if it's not 'pure' OO or whatever, I'd chose that over duplicating code all over the place. 1: I know Java isn't fashionable, but for many reasons, it's perfect for this kind of project.
- akyu 8y agoBeen hacking on a side project in Go, after coming from a career of nearly exclusively using OO languages, with a little bit of functional here and there. And of course enjoying all of the new functional features that nearly every big language has been racing to implement these days. At first there I had some quirks, along the lines of "how do I get thing X from A to B", or "these things are similar how do I share code between them". Everytime this happened I couldn't help but go through a process of solving the problem in OO and then unwrapping it into a non-OO solution. Which doesn't always turn out well. Eventually, once I became comfortable with Go, I got to the point where I just code things in the most obvious way possible. This struct has these fields. This function takes x and produces y. I dont try to take a big picture view of the software as a whole, Im just solving a bunch of small localized problems one after the other. Then I refactor periodically to get rid of anything completely stupid, and make sure the big picture looks ok. The name of the language is quite fitting, you just go. Whereas in OO, for me at least, it feels like im constantly bouncing all around the abstraction layers of your program. Any change I make here in class X could affect these other classes. Class A is dependent on Y, but so is class B. How do I share it between them? Maybe dependency injection? Subclasses? Do I refactor to an abstract class? Wait, should it be an abstract class or an interface, what's the difference again? Is this generic enough? I only need to use this in this one place, but I'll make it generic because "best practices". What if I might need this other subclass eventually but it's not on the spec right now? I'm obviously exaggerating, but I think most of us have been there. I find I often end up in a competition against myself about being more clever or more reusable with my OO code. Our job is already hard enough when you have to decide and reason about algorithms, data structures, concurrency etc. The extra layer of code patterns usually just obscures things. I'm not a complete hater though. Using a well designed OO library can be an absolute delight (PyTorch being a great example). But OO is the kind of thing were its not just garbage in garbage out. The garbage multiplies and creates even more garbage.
- mharroun 8y agoI guess I'm old school but I have had huge success with OOP and the long lived design patterns. Patterns like adapter, bridge, and strategy are emensly useful in archetecting software that will last more then the next iteration or 6 months. Perhaps I havent seen the right resources but so far function programming seem weak to me compared to oop which has things like domain driven design, and a very long list of battle tested design patterns.
- jyosh 8y agoprefer composition for code reuse and inheritance for polymorphism
- dennisgorelik 8y agoThe main problem that inheritance introduces -- is irrelevant references to methods and fields. If I want to see what code is using a method I am interested in, Visual Studio shows me all related code. But if other developers overrode that method in their own classes, then in addition to seeing ~5 references I like to see, I have to sift through ~50 other references that are irrelevant to my investigation. So 5 minutes investigation turns into 55 minutes investigation. As you can see, that "references spam" can easily slow down research and development 10x or more.