15 ms·
I feel people don't understand what inheritance and (object orientation in general) is useful for, misuse it, and then it gets a bad reputation. It's not about
by captainmuon 3y ago
I feel people don't understand what inheritance and (object orientation in general) is useful for, misuse it, and then it gets a bad reputation. It's not about making nice hierarchies of Cars, Fruits, and Ovals.
For me the main point is (runtime) polymorphism. E.g. you have a function that takes a general type, and you can pass multiple specific types and it will do the right thing. And if you want to avoid huge if-else statements, you should put the code for the special cases in the classes, not in each function that operates on them.
You can get this without implementation inheritance, it is also possible to just have something like interfaces. But I do find it very convenient to put common code in a base class.
- bluetomcat 3y agoPolymorphism is doable in plain old C with lookup tables and function pointers. If that is the only benefit, what is the point of creating a language where everything is an object?
- mrkeen 3y ago> Polymorphism is doable in plain old C with lookup tables and function pointers Not without casting. qsort is still: void qsort_r(void *base, size_t nmemb, size_t size, int (*compar)(const void *, const void *, void *), void *arg);
- chuckadams 2y agoPresumably one would write an object-oriented version of quicksort to go with their OO C library design and not use the stdlib functions. For example GObject is OOP in C, and I imagine it has collection types with sort methods. Inheritance is what will bite hard when hand-rolling OOP in C. For one, you can forget about the compiler enforcing substitutability and co/contravariance for you.
- vidarh 2y agoBecause syntactic sugar and abstractions matter. You can do everything in assembly too, yet we prefer something higher level.
- michaelrpeskin 2y ago> what is the point of creating a language where everything is an object? I think that's the ultimate culprit in everyone hating inheritance. If it weren't for Java, I think we'd all have a healthier view of OO in general. I learned OO with C++ (pre C++-11), and now I work at a Java shop, but I'm luck that I get to write R&D code in whatever I need to, and I spend most of my time in Python. In C++ and Python, you get to pick the best tool for the job. If I just need a simple function, I use it. If I need run-time polymorphism, I can use it. If I need duck-typing I can do it (in Python). Without the need for strict rules (always/never do inheritance) I can pick what makes the best (fastest? most readable? most maintainable? most extensible? - It depends on context) code for the job. Related to TFA, I rarely use inheritance because it doesn't make sense (unless you shoehorn it in like everyone in the threat is complaining about). But in the cases where it really does work (there really is a "is a" relation), then it does make life easier and it is the right thing. Context matters, and human judgement and experience is often better than rules.
- Kranar 2y agoC++ templates are a form of duck typing as well, and combined with type erasure give you a lot of the benefits of OO without the downsides.
- michaelrpeskin 2y agoYes, you're right. I've done that in high-performance code where I couldn't afford the double function call of a virtual function. I forgot about that.
- jpc0 2y agoLikewise this is getting easier with the auto keyword now being sugar for templates when used in function arguments and return values
- igouy 2y agoI learned OO with Smalltalk. Everything is an object so there's no if-it's-an-object do this but if-it's-not-an-object do that.
- igouy 2y agoSo we might guess that the language designers thought there were other benefits.
- igouy 2y ago1981 "Design Principles Behind Smalltalk" https://www.cs.virginia.edu/~evans/cs655/readings/smalltalk.html https://www.cs.virginia.edu/~evans/cs655/readings/smalltalk....
- UncleMeat 2y agoControl flow is doable with gotos. What benefit therefore is structured programming? Dynamic dispatch is implemented under the hood with lookup tables and function pointers. Sometimes, it is nice for a language to wrap a fiddly thing in a more abstract structure to make it easier to read, understand, and write.
- ano-ther 3y agoYes. I have a framework for an embedded system that uses various types of sensors. When changing a sensor, instead of rewriting the polling loop for every new case, I can keep looping through ‘sensor[i]->sampleNow()’ and add the specifics in a class inheriting from SensorClass.
- touisteur 2y agoInterface inheritance could do the trick then? Maybe function pointers even.
- mrkeen 3y ago> For me the main point is (runtime) polymorphism. E.g. you have a function that takes a general type, and you can pass multiple specific types and it will do the right thing. The runtime part is what I dislike. If I have a fruit which is an apple or a banana, I can't pass that to a method expecting an apple or banana. It can only be passed as a fruit. > And if you want to avoid huge if-else statements, you should put the code for the special cases in the classes, not in each function that operates on them. This is common OO wisdom that I strongly disagree with. For example, in my program I have a few types (Application, Abstraction, Variable, etc.), and a lot of transformations to perform on those types (TypeCheck, AnfConvert, ClosureConvert, LambdaLift, etc.). I prefer to have all the type-checking code inside the TypeCheck module, and all the closure-converting code inside the ClosureConvert module. I'd take the "huge if-else" statements inside TypeCheck rather than scatter typechecking across all my datatypes.
- rtz121 2y ago> If I have a fruit which is an apple or a banana, I can't pass that to a method expecting an apple or banana. It can only be passed as a fruit. You can by overriding the method on apple or banana. If your method is on some other object, then yes, you cannot do this unless your programming language supports multiple dispatch.
- zbentley 2y ago> I prefer to have all the type-checking code inside the TypeCheck module There are ways to have your cake and eat it too, here, at least in some languages and patterns. For example, in Go you could define "CheckType" as part of the interface contract, but group all implementors' versions of that method in the same file, calling out to nearby private helper functions for common logic. Ruby's open classes and Rust's multiple "impl" blocks can also achieve similar behavior. And yeah, sure, some folks will respond with "but go isn't OO", but that's silly. Modelling polymorphic behavior across different data-bearing objects which can be addressed as implementations of the same abstract type quacks, very loudly, like a duck in this case.
- Capricorn2481 2y ago
- parpfish 2y agoif i were writing an intro to programming book, i would introduce OO as a means of building encapsulation. i'd only get into inheritance in later chapters.
- gentleman11 2y agoYou can have encapsulation without oop. Polymorphism is the real benefit of oop imho
- gorjusborg 2y ago> Polymorphism is the real benefit of oop imho It's pretty much the definition of OOP. The core feature of OOP is just bundling functions with the state it processes. When you bundle state and functions together, you can't predict what calling the function will do without knowing both the code and state. You can say its 'the real benefit', I guess, but that feels like circular reasoning. Its pretty much the definition of what OOP is, so calling it a benefit feels weird. Unfortunately, designing systems as a collection of distributed state machines tends to become maintenance nightmare. Functions and data being separated tends to make code better, even when working in so-called 'OOP' languages.
- jcelerier 2y ago> You can say its 'the real benefit', I guess, but that feels like circular reasoning. Its pretty much the definition of what OOP is, so calling it a benefit feels weird. It's a benefit compared to how people were writing code before it became mainstream (for polymorphism: by jumping to data-defined parts of the code and hoping for the best, and for encapsulation: subroutines working on global variables).
- tubthumper8 2y agoWhich kind of polymorphism? Subtype polymorphism I'm guessing?
- eddd-ddde 2y agoExcept inheritance is the premature optimisation of interfaces. Inheritance forces you to define the world in terms of strict tree hierarchies, which is very easy to get wrong. You may even do a great job today, but tomorrow such properties don't hold anymore. Regular composition allows the same functionality without making such strong assumptions on the data you are modelling.
- sameoldtune 2y agoArborescent vs rhizomatic approaches, to get philosophical.
- 082349872349872 2y agoUnfortunately Deleuze's The Fold has little to say about either awk or catamorphisms.
- dragonwriter 2y ago> Inheritance forces you to define the world in terms of strict tree hierarchies, No, it doesn’t. Inheritance is the outcome of deciding to model some part of the problem space with a tree hierarchy (that potentially intersects other such heirarchies). It doesn’t force you to do anything. I suppose if there was a methodology which forced you, as the only modeling method, to exclusively use single inheritance, that would force you to do what you describe, but…that’s not inheritance, that’s a bunch of weird things layered on top of it.
- eddd-ddde 2y agoIt may not force you directly, but I think it's safe to say that when a language focuses on inheritance (e.g. c++) it does not offer good alternatives (e.g. rust traits). This means that if you want features such as runtime polymorphism, you are _kinda_ forced into inheritance.
- EVa5I7bHFq9mnYK 2y agoTrue. Modelling the world as a tree oversimplifies it, as there are also lateral and even backwards dependencies, and at a sufficiently complex level abstractions start to leak all over, making it a mess. But at some low to medium level of complexity it might work.
- sameoldtune 2y agoYou’re describing the strategy pattern, which is probably one of the most practical coding design patterns. Ex: each chess AI difficulty gets its own class, which all extend a common interface.
- krapht 2y agoI still find it funny that somehow the idea of runtime function dispatch via function pointer is called the "strategy" pattern.
- sameoldtune 2y agoHey I didn’t name it!
- VirusNewbie 2y ago>For me the main point is (runtime) polymorphism. But you don't actually care about runtime polymorphism here. You care about polymorphic behavior, which can be implemented in a much more composable way with parametric polymorphism.
- layer8 2y agoYou can’t build a dynamic list of objects implementing the same interface in different ways with parametric polymorphism. As another example, the Unix file interface (open(), read(), write(), flush(), close()) etc. is an example of runtime polymorphism, where the file descriptors identify objects with different implementations (depending on whether it’s a regular file, a directory, a pipe, a symbolic link, a device, and so on). All operating systems and many libraries tend to follow this pattern: You can create objects to which you receive a handle, and then you perform various operations on the objects by passing the respective handle to the respective operation, and under the hood different objects will map to different implementations of the operation.
- VirusNewbie 2y ago>You can’t build a dynamic list of objects implementing the same interface in different ways with parametric polymorphism. Yes you can. that's the whole point of type classes.
- layer8 2y agoNot without runtime polymorphism. Parametric polymorphism does not imply nor by itself implement runtime polymorphism. I.e. C++ templates, or generics in other languages, provide parametric polymorphism, but not runtime polymorphism.
- gpderetta 2y agoUniversal Vs existential qualification. For example in c++ std::function exhibits parametric polymorphism without templates.
- dblohm7 2y agoUnfortunately a lot of developers are conditioned so heavily to believe that inheritance is intrinsically bad, that they contort their code into an unreadable mess just to re-implement something that could have been trivial to do with inheritance. I'm not saying that I like it everywhere; IMHO it's a tool to be used sparingly and not with deep hierarchies. But it's not reasonable to avoid it 100% of the time because we're soiling ourselves over the thought of possibly encountering the diamond problem.
- tombert 2y agoI feel like Clojure-style multimethods accomplish this better than inheritance. I can simply write a dispatcher that dispatches the correct function based on some kind of input. This is evaluated at runtime, thus giving me the runtime polymorphism, but doesn't make me muck with any kind of taxonomy trees. I can also add my own methods that work with the dispatcher, without having to modify the original code. I don't feel like it's any less convenient than inheritance, and it can be a lot more flexible. That said, I suspect it performs worse, so pick your poison.
- BeetleB 2y ago> I feel people don't understand ... > For me ... Sorry, but right off the bat this is just painting you as an example of "There are N camps, and each camp declares the other camp as wrong." Yes, that's what inheritance is for you. For others it is something else. Why is your way the one that "correctly understands" it? The article itself covers this - that some languages have lumped 3 different concepts into one that they call inheritance, leading to the different camps and comments like yours. Your camp is specifically mentioned: > Abstract data type inheritance is about substitution: this thing behaves in all the ways that thing does and has this behaviour (this is the Liskov substitution principle)
- CharlieDigital 2y agoI feel people don't understand what inheritance and (object orientation in general) is useful for, misuse it, and then it gets a bad reputation. It's not about making nice hierarchies of Cars, Fruits, and Ovals. Agree 100%. It starts from the earliest programming course where we just teach it all wrong; way to abstract (no pun intended). One point to add to yours: well executed OOP allows for "structural" flow of control where the object hierarchy, inheritance, events, and overrides allow for the "structure" of the hierarchy to control the flow of logic. I wrote two articles on this with concrete examples and use cases: https://medium.com/codex/structural-control-flow-with-object-oriented-programming-part-2-7d18526146de https://medium.com/codex/structural-control-flow-with-object... https://medium.com/@chrlschn/weve-been-teaching-object-oriented-programming-all-wrong-part-1-e171f57aa209 https://medium.com/@chrlschn/weve-been-teaching-object-orien...
- hahn-kev 2y agoRead the first article, great example, I'll have to remember that next time I'm trying to teach OOP
- tubthumper8 2y agoInteresting articles! I think the first part of the first article immediately introduces an anti-pattern - forcing the user to make an instance of a class just to be able to call a pure function. It's just unnecessary noise, either make them static methods, group them in an object literal, or import the module as a namespace. Adding the "pattern" as a mutable public field is a bit sketchy, and would make it show up in intellisense, but hopefully nobody will access/modify it. Making the pattern as a `const` at module scope solves that problem but you handwave away that approach saying it "pollute the symbol space for intellisense", which isn't true (non-exported items aren't available outside the module). The next section on validation is a good example of another anti-pattern. The example of: public get isUserValid(): boolean is not a good way of doing it, because it relies on hopes and prayers that the user of the class remembers to call this. A better signature would be: function isUser(input: unknown): input is User Notice the key difference - you can't get an instance of User without it being valid. There shouldn't be a notion of "yeah I have a User, but I don't know if it's a valid User". Your way allows people to operate with User, blissfully unaware of whether it is valid or not, hoping that they might notice the right method to call. Instead: Parse, Don't Validate [1] Note that to do this correctly, you have to either: 1. Separate data and functions 2. If you must use a class, then hide the constructor and provide a static constructor with a return type that indicates the function can fail [1] https://lexi-lambda.github.io/blog/2019/11/05/parse-don-t-validate/ https://lexi-lambda.github.io/blog/2019/11/05/parse-don-t-va... _________ Part 2 was an interesting exercise in how the popular OOP patterns from the GoF book vastly overcomplicated code, negatively impact readability, and make following the code feel like Mario (the princess is in another castle) No need for inheritance, abstract base classes, of any of that complexity, all of that could be done with: type ShippingCalculator = () => number const shippingCalculators = { USPS: calculateUSPS, UPS: calculateUPS, FedEx: calculateFedEx, DHL: calculateDHL, } as const satisfies Record<ShippingMethod, ShippingCalculator> Intellisense works fine of course, and code navigation (via F12) is straightforward and easy to navigate.
- quaunaut 2y agoIf people get it wrong so regularly, what value is it providing as a concept? These concepts are supposed to help us reach something better, if you have to add 30 caveats to every part of it, all it did was hide its own complexity from you, instead of managing it for you.
- skydhash 2y agoBecause the tree is a nice abstraction for some problems. But sometimes you need a collection of pure functions. Sometimes it’s best to think of your object as data blobs going through a line of map,filter,reduce functions. Not every part of your application is the same, use the right abstraction for the job.
- bamboozled 2y agoThis is why "interfaces" is a better word and and a better concept to describe this paradigm, not inheritance.
- Sankozi 2y agoYou can have polymorphism without inheritance and inheritance without polymorphism. The big problem with inheritance is that it is really tricky to write those base classes. Making class inheritable by default in programming language is often considered a big mistake, because only carefully designed and documented class can be safely extended.
- commandlinefan 2y agoI see that a lot - the alternative to inheritance, when inheritance does make sense, is code duplication, which is much worse than inheritance, or first-order functions, which many languages don't actually support or don't support efficiently.
- Capricorn2481 2y ago> you have a function that takes a general type, and you can pass multiple specific types and it will do the right thing What does this have to do with inheritance? This is just a generic function. > And if you want to avoid huge if-else statements, you should put the code for the special cases in the classes, not in each function that operates on them This doesn't sound any different from how people usually talk about inheritance. I am not convinced it gives you anything more than composition does. Composition is very easy to setup and easy to change. And there are certainly functional ways to do runtime polymorphism.
- SpaghettiCthulu 2y ago> > you have a function that takes a general type, and you can pass multiple specific types and it will do the right thing > > What does this have to do with inheritance? This is just a generic function. It's a generic function with compile-time compatibility checks.