4 ms·
Inheritance is usually not the solution; composition and delegation is. Unfortunately, the current landscape of popular languages have equated "object programmi
by trashburger 3y ago
Inheritance is usually not the solution; composition and delegation is. Unfortunately, the current landscape of popular languages have equated "object programming == inheritance" in people's minds. The fact that these languages make inheritance easy but provide no syntactical or semantic support for composition and delegating to inner objects compounds this problem.
> there could be an element inheriting low-level behavior (HTTP/API call, etc) from a button and a high-level behavior (style/presentation, etc) from a visual class.
This becomes very messy very quickly, and you would need to clearly separate the concerns to make them non-expressible on either side. CSS and JS make this possible by being two different languages, one Turing-incomplete (in usual scenarios).
- frou_dh 3y agoI think of inheritance as just being a workaday implementation detail to facilitate code reuse in some scenarios. Polymorphism ("many forms") is more related to the concept of interfaces, but as you touch on has been equated with inheritance in a lot of peoples' minds.
- _a_a_a_ 3y agoInheritance should be used for interfaces, not code reuse (unless that comes free with it).
- nradov 3y agoThen how do you avoid duplicate code without using inheritance? Other solutions like placing shared functions in "helper" classes are far worse from a complexity and maintainability standpoint.
- javcasas 3y ago"helper" classes are a symptom of an object-obsessed programming language, a language where functions can't live in a module, they must always exist in a class. Especially if it doesn't make sense for a class to hold them. You avoid duplicated code by making generic functions, putting them in modules, and making your objects accept them as parameters.
- nradov 3y agoWell I've done that before and the results were no better than using inheritance. What's the point?
- _a_a_a_ 3y agoI guess it's like this – implement interfaces, and common code comes with it when you inherit (then override to modify behaviour). A common interface really should have common code so this is appropriate. What is wrong is to inherit from an object with a different interface just to get your hands on the code - that might seem obvious but when OOP started to be used in industry, that was acceptable or even recommended practice. That malpractice was formalised away by Barbara Liskov with the Liskov principle. I'm pretty certain you're doing the right thing already so it was a clarification rather than a criticism.
- magicalhippo 3y agoIn Delphi, this is my preferred way. I use interfaces[1]for the public-facing stuff, but I might use inheritance to implement the interfaces for code reuse and ease. I of course also use delegation a lot, but for some things I feel inheritance is a better fit. Regardless, the inheritance remains an implementation detail. [1]: https://docwiki.embarcadero.com/RADStudio/Alexandria/en/Interface_References_(Delphi) https://docwiki.embarcadero.com/RADStudio/Alexandria/en/Inte...
- _a_a_a_ 3y ago> make inheritance easy but provide no syntactical or semantic support for composition and delegating to inner objects compounds this problem I admit I would really like a 'delegate function call to <object>' construct, but excuse me for being dense, what composition are you referring to? Other than inheritance and delegation, what else is there[1] , and what would a suitable construct supporting it in the language look like? TIA [1] (there is functional-style composition, but that's perfectly well supported in many decent languages like Scala)
- Tmpod 3y ago> I admit I would really like a 'delegate function call to <object>' construct Yeah, I'd love to see Kotlin's delegation[0] developed further and appear in other languages as well. [0]: I admit I would really like a 'delegate function call to <object>' construct
- mike_hearn 3y agoKotlin's delegation feature is useful sometimes, but OOP inheritance still crops up more often. One reason is that inheritance is strictly more powerful. When you delegate (compose), you can't modify the inner working of the object you delegated to, you can only wrap it. Often that's not sufficient. You want to actually customize the implementation in some way. OOP's inheritance is ideal for this, because the superclass can clearly define chunks of functionality that subclasses are allowed to either (a) replace entirely or (b) wrap or (c) provide (abstract methods). This is a very flexible approach. With mere delegation you can only wrap, and only for method calls that originate outside the object, and only for public methods. It's also clean. The distinction between what you must implement, what you can implement and what is for the user is defined in the type system and enforced by the compiler. Additionally you separate the interface for customizations from the interface for users (protected vs public), which keeps documentation and auto complete properly focused. When combined with dependency injection and code generation it also allows third party frameworks and libraries to enhance the code you write in various ways, that remain mostly tucked out of the way but can be revealed in an IDE in an instant (and a good IDE will show visual hints that there are such customizations in effect). The final advantage is that in some languages the implementation is highly optimized. On HN you'd be forgiven for thinking nobody writes virtual method calls since 20 years ago but in reality virtual method dispatch is so common in real programs that it's heavily optimized by both CPUs (sophisticated indirect branch caching) and language runtimes (PICs in fast VMs like Hotspot or V8). Composition is less well optimized. Inheritance gets an overly bad rap on Hacker News, despite how prevalent it is. I think this is because it's so flexible you can easily make a mess with it, and some people have. Also the decision of Java to make all classes and methods open by default means overriding is often used as a hacky way to hot-patch libraries in the field, rather than as part of any principled overall design. Kotlin reverses that decision to some extent and so classes and methods are all final by default, unless a compiler plugin overrides that. I also suspect the move to web development has had this effect, because in most classical OOP toolkits you are allowed or even encouraged to extend the UI by subclassing pre-existing node types. So you can make a custom button by subclassing Button or whatever. HTML is implemented using classical OOP designs, but mostly for implementation reasons that isn't exposed to the web developer who instead is expected to customize the controls purely through CSS and setting properties. If you want to make a custom button widget, well that's too bad, better learn React or something similar. So people just aren't exposed to the situations where OOP is useful because browsers weren't designed for UI, and the memory of why it was originally developed atrophies. And some of it is just memeing. OK, now tell me why I'm wrong.
- ernst_klim 3y ago> Unfortunately, the current landscape of popular languages have equated "object programming == inheritance" in people's minds. Because OOP is somewhat equals to inheritance (or more strictly late binding + open recursion + self-polymorphism, inheritance is just one way to achieve that). "delegating" is completely orthogonal to OOP, you can have delegates in any modular language, like ML. Saying "you need to use more delegates and less inheritance" basically equals to "you need to write less OOP code".
- crabmusket 3y ago> you can have delegates in any modular language, like ML I'm struggling to understand this, could you give an example?
- ernst_klim 3y agomodule type Repo = sig type t type user val get_user : t -> name:string -> user option val get_all_users : t -> user list end module LoggedRepo (R : Repo) : sig (* Resulting module is of type Repo plus create function *) include Repo val create : delegate:R.t -> logger:Logger.t -> t end = struct type t = { delegate : R.t; logger : Logger.t } type user = R.user let create ~delegate ~logger = { delegate; logger } let get_user repo name = Logger.debug repo.logger "Get user by name"; R.get_user repo.delegate name let get_all_users repo = Logger.debug repo.logger "Get all users"; R.get_all_users repo.delegate end In languages like C it would be harder since they don't have notion of interface and implementation, so you would have to implement that as an object-like structure of function pointers and opaque structs for state. The point is, for delegate you don't need lately bound methods and virtual dispatching, i.e. oop, only the notion of interface and implementation supported (or emulated), and then you can create implementations that wrap another implementations.
- _old_dude_ 3y agoYou still need virtual dispatch (or type pattern matching) if there is a variable which can contain either a BDRepo or a LoggedRepo(BDRepo).
- mrkeen 3y ago> Unfortunately, the current landscape of popular languages have equated "object programming == inheritance" in people's minds. That's because inheritance isn't really elsewhere, making it uniquely an OO concept (though I'm sure there are exceptions). When people say OO is all about X, Y, and inheritance, well, X and Y are in most of the other languages too.
- BurningFrog 3y agoThe big "X" is that the code is implemented by objects calling each other. That's what OO is to me. Inheritance is nice for some things, but entirely optional.
- Gibbon1 3y agoPeanut gallery comment. Generics without type erasure and duck typing makes inheritance an antipattern.
- jrumbut 3y agoWe forget that favoring composition over inheritance is one of the core ideas of OOP. It's supposed to be, at least. Textbooks have an example like "Dog inherits from Animal" as a way to demonstrate the concept of inheritance, but people took that as an invitation to become Linnaeus and create a hierarchical taxonomy of their domain. It might be a useful exercise, actually, but it's rarely the best way to design software and you can do OOP (and do it better) with sparing use of inheritance (by replacing it with extensive use of composition).