4 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 CharlieDigital 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.
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 3y agoRead the first article, great example, I'll have to remember that next time I'm trying to teach OOP
- tubthumper8 3y 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.
- ejflick 3y ago> "yeah I have a User, but I don't know if it's a valid User". This, imo, is one of the big reasons people so easily dismiss OOP. They put whatever data _they think they probably need_ in an object using setters/builders/what have you. This leads to abstractions of data that don't accurately reflect state. They will then let an external entity (service or whatever pattern) manipulate this data. At this point people might as well use something analogous to a C struct where anything can be done to the values. Objects are not managing their own invariants. When you rip this responsibility from an object, then nothing becomes responsible for the validity of an object. Due to this, people wonder why they get bugs and have trouble growing their software. This also leads to things like "isValid". People don't understand that an object should be constructed with only valid data. The best example I've found of protecting variants and construction of valid objects to be this strategy in F#: https://fsharpforfunandprofit.com/series/designing-with-types/ https://fsharpforfunandprofit.com/series/designing-with-type... I'm yet to find a good way to do this in a language such as Java unfortunately.
- CharlieDigital 3y agoIn psychology, there is an idea of "locus of control". I think OOP done well and applied in suitable scenarios results in entities that have internal locus of control; it's mutations of state are internally managed. OOP done poorly has external locus of control where some external "service" or "util" or "helper" manages the mutation of state.
- ElevenLathe 3y agoI used to be an OO hater until I started playing with Smalltalk. In particular, I worked through the Pharo MOOC (https://mooc.pharo.org/ https://mooc.pharo.org/), which teaches you exactly this: designing the hierarchy IS designing the control flow of the program. That said, Smalltalky hierarchies are a nightmare in most other languages because another key part of the Smalltalk env is the tooling -- staying inside a running system and being able to edit code from within the debugger is absolutely great and keeps you in a flow state much better than any other workflow I've been exposed to (including Lisp + Paredit + SLIME). The result is that editing class hierarchies in blub-y OO languages is usually a massive pain in the ass, while doing it in Pharo or a similar Smalltalk env is fun and painless. This is why you can't practically write Smalltalkly in Python/C#/Java/etc. even if on paper they have all or almost all of the same features.