7 ms·
I find "modern" OOP concepts apply pretty well to graphical user interfaces. I believe that a good UI has a consistent and relatively strict hierarchy, with few
by freetonik 3y ago
I find "modern" OOP concepts apply pretty well to graphical user interfaces. I believe that a good UI has a consistent and relatively strict hierarchy, with few "unique snowflake" elements. Even such a divisive idea as multiple inheritance often makes sense in UI, i.e. 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.
(In a way, even CSS is kind of OOP, albeit with very confusing overriding rules.)
When writing generic backend code in a language like Java, I often found fighting against OOP because the business logic is not necessarily mappable to "dog inherits from animal". And I can't change the business logic, so OOP has to give in. But when developing user interfaces, I am usually in more control, I am actually designing it, not observing directly from the business logic. And OOP forces me to have a more consistent implementation, which is beneficial for the user, because consistent UI makes it easy for people to get used to it, find the underlying logic in it, and start relying on it. If all links on all web pages would inherit from normal HTML href link (like it was designed in the beginning), we wouldn't suffer from those weird JS web apps where you can't open a link in a new page for example.
- trashburger 3y agoInheritance 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 ago
- _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).
- bob1029 3y agoThe #1 canary for me is circular dependencies. In more complex domains, a strict hierarchy of types begins to fall apart rapidly. In my experience, a relational model is the best solution for domain modeling precisely because it can directly represent circular dependencies without any sort of weird interface hacks, one off Init() methods, etc. A practical example of this would be something like the domain of banking. In a bank, you generally have 2 major types of entities: accounts and customers. Accounts can have many customers and customers can have many accounts. Which type should "win"? In SQL you don't have to make this decision at modeling time - it is deferred to the caller and its specific view of the world. You can certainly emulate this in OOP, but then you are stuck doing joins between collections manually.
- Xcelerate 3y agoThis is why I prefer multiple dispatch over classes: functions dispatch on the types of all arguments, not just the type of the implicit first argument. After first encountering this paradigm in Julia, it’s painful to use other languages that don’t have this feature.
- JanisErdmanis 3y agoI don’t think multiple dispatch solves this issue. Julia does have a strong type hierarchy and the proposed example with customers and accounts would be likewise hard to model. IMO only Rust like traits solves this issue elegantly.
- bob1029 3y ago> solves this issue elegantly What about cycles? For example, "List all distinct customers from all accounts that are owned by customers who are members of some other subset of accounts".
- JanisErdmanis 3y agoI agree Rust does not make it easier either. This is an interesting design problem. I tried it with ChatGPT and it did produce a robust solution. This does not seem to be a limitation for strong type hierarchy after all: ``` # Define a struct for Customer struct Customer id::Int name::String # Other customer-related fields end # Define a struct for Account struct Account id::Int balance::Float64 type::String # e.g., "Savings", "Checking" # Other account-related fields end # Define a struct to manage the many-to-many relationship struct CustomerAccount customer::Customer account::Account end # Define a struct for the Bank which holds all information struct Bank customers::Dict{Int, Customer} accounts::Dict{Int, Account} customerAccounts::Vector{CustomerAccount} end ```
- uticus 3y agoI understand what you mean but at the same time in the web-dev world I find it interesting that React is migrating from OOP to functional. ("We recommend defining components as functions instead of classes." at top of page at https://react.dev/reference/react/Component https://react.dev/reference/react/Component)
- codeptualize 3y agoReact has always used functional programming patterns. They used classes to define components, but even those applied functional principles.
- meheleventyone 3y agoFrom the looks of things the main benefit is less boilerplate? What are the other benefits from beyond this?
- codeptualize 3y agoMainly hooks. Hooks make it easy to reuse component logic, something that was difficult with class components. You’d need HOC’s which are hard to deal with, especially with prop collisions and refs. (This change has little to do with OOP vs functional. React calls them function components, not functional. React has always used functional patterns, even in the class components.)
- meheleventyone 3y agoThanks I went and looked up the different approaches (have never written a line of React in my life) and I definitely like the hooks more on first blush.
- mike_hearn 3y agoReact isn't actually functional. It's OOP in disguise. There's no such thing as functional UI, in the classical sense of FP. It's one of the weaknesses of FP. Although React tutorials start by saying your UI becomes a pure function of your app's state, React components do have encapsulated state. Any time you call useState inside a react function you're doing something that doesn't exist in real FP programs. Additionally React boils down to a way to create DOM elements, which are highly stateful C++ objects exposed to JavaScript, implemented using inheritance: https://source.chromium.org/chromium/chromium/src/+/main:third_party/blink/renderer/core/html/forms/html_button_element.h;drc=b5fa1427d7ab0f37a5ea6f61fae9eec9fbda460d;l=34 https://source.chromium.org/chromium/chromium/src/+/main:thi... An HTMLButtonElement is a HTMLFormControlElement is a HTMLElement (and a ListedElement and a FormAssociated). The rules for how you write code in React (or Jetpack Compose) violate even the most basic rules of the host language. useState defines or returns what is effectively an object field (it's data associated with that widget), but it looks syntactically like a local variable. It's not a local variable though because its value will survive the exit of the scope. Nor is useState a normal function call because there's a hidden invocation counter used to work out what state slot you want, which is why you aren't allowed to call it inside conditional branches and why loops need special handling. So in React's "functional style" we can see that you're actually defining objects with encapsulated state, that register event handlers, that export properties and so on. It's just obfuscated to look like it's not OOP.
- ernst_klim 3y ago> I find "modern" OOP concepts apply pretty well to graphical user interfaces. Though most contemporary frameworks (react, elm, android compose) tend to prefer (functional) reactive programming.
- Scarblac 3y agoThat's also because they exist in a browser. The browser displays one page (from the viewpoint of a web app) and has one global event loop, the event loop calls into the application code. Application code doesn't run at other times. Reactive programming is a good fit for that.
- PaulDavisThe1st 3y ago> one global event loop, the event loop calls into the application code. Application code doesn't run at other times. In the 80s we called that "event-driven programming", and until threads became widely available, it is how all native GUI programming worked, more or less.
- marcosdumay 3y agoEvent-driven is the "reactive" part only. And the old implementations of it where so less capable of fine-handling the events and complex dependencies that it's reasonable to change the name.
- PaulDavisThe1st 3y agoI would disagree. Conventional desktop GUI toolkits offer much more fine-handling of events and complex event/data dependencies. Web front ends are now approaching those levels, though from a different direction.
- codeptualize 3y agoI disagree on the UI side. UI has been moving towards more functional declarative approaches as it’s much easier to manage. CSS is also interesting, we mostly try to avoid the inheritance with scoping, utility classes and or css in js solutions as it becomes really hard to manage otherwise.
- marcosdumay 3y ago> In a way, even CSS is kind of OOP Inheritance makes for a very powerless form of logical programing. CSS is way more than OOP. (Powerless is always preferred if it's sufficient, but CSS clearly shows a single hierarchy isn't sufficient.)
- nine_k 3y agoInheritance is likely the worst idea of OOP's implementations. The right way to achieve the same and better results is interfaces / typeclasses / traits. They work pretty well in languages from Rust and Haskell to Typescript, Kotlin, Java, and even Go. (Go is a bit special because you don't even have to declare that your class implements an interface, as long as it actually has a set of methods with right signatures.) Ideally, all public methods should only describe parameters and return values in terms of interfaces, and never as concrete classes. If you need to take a big class and inherit from it to override one or two methods, it's a failure to compose the things correctly. These one or two methods usually are better off as their own entity which you can pass to the "base" as parameters. This both prevents proliferation of "god objects", increases testability, and improves build times (shallower dependency graph, yay).
- 38 3y ago> Ideally, all public methods should only describe parameters and return values in terms of interfaces, and never as concrete classes. Citation needed. I've seen interface heavy Go code, and without fail it's always unreadable garbage. Interfaces are best used in moderation, at API boundaries only.
- deleted 3y ago[deleted]