4 ms·
Interesting 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
by tubthumper8 3y ago
Interesting 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.