After 3 years, this video continues on helping people understand these concepts and I'm an example of that. Thank you so much! Instantly subscribed after the video.
Thank you so much for your perspective. This type of growth would never have ocurred to me without someone of your expertise mentioning it. Lots of gratitude!!!!!
Thank you! We need more content like this, building node APIs with TS and design pattern/SOLID principles. A suggestion: As someone already pointed out, your implementation of the factory pattern is not full. It's more of a facade. You'd need arguments (enums for instance) for the factory to allow for implementations of different databases types on the fly.
Great tutorial. Next one could be how to organise medium to large projects in a maintainable way. All I know is Uncle Bob's domain driven design, but this seems a bit overkill for me...
Really useful to see these patterns that I hear my backend/Java colleagues talking about. Though, I'm curious if they are still considered best practice in TS / modern JavaScript. I'm struggling to think of scenarios where these patterns work better than more simple, easier to read/reason.
I think i would rather implement all this by using: - Map for inMemoryDb - EventTarget + CustomEvent for the pub/sub logic - Proxy to proxy get and set methods of Map with pub sub events
For the singleton, i'd say "the C++'y way" you mentioned after was the only actual legit way, saying "call it once and away you go" just means it's a factory producing an instance each time, not, a singleton, only when you were returning the same instance did it become singleton. // Singleton const db = new InMemoryDatabase(); return db; At that point it was not a singleton as far as I understand it. Each time that will be run you get a new instance.
At around 16:46 I talked about, but didn't demonstrate, that you would call the createDatabase once in another file and then store the result as a const and return it as a default export. And that const value would then be a singleton. Sorry, I should have demonstrated that completely.
@@jherr ah nice. Awesome content btw also maybe you already covered it in another video but so many people ask, "how do i make the code do this depending on the type" i.e. runtime typechecking which isn't a thing, only primitives like string and bools and nums This question is so pervasive I think there is space for a video about why types and logic go alongside each other but are not interchangeable. Typescript is fancy documentation, thus it would be like altering the code logic via documentation.
What are you using to bootstrap little typescript projects like this? I see you're using ts-node, but is there other tooling that spits out the bare project?
Hi Jack, Awesome video! One comment though, I tried the Singleton pattern you created here and it is not working. After some investigation it turns out that since the class is returned inside a function, it has a different scope every time you call the createDatabase function. Therefore it is never a singleton. Also after some more investigation, you can't actually create a generic singleton class since it undermines the pattern itself.
Yeah, I glossed over this too fast. My point was that you call the function once and store the result in a constant, and then export that constant as the default export for the module.
Thanks for this lecture. I tried out the code but the visitor pattern had to declare item:any as a workaround I was getting an error : " Argument of type '(item: T) => void' is not assignable to parameter of type '(value: unknown, index: number, array: unknown[]) => void'.". if I used item: T as per the interface. But its a small issue, the code was a great exercise.
isn't it that, with the singleton as you implement it... every time you call the "instance" static method, you actually instantiate the db again? Think the gist of the pattern is that you prevent that by checking in the "instance" method if the class has already been instantiated and if so, then returning an already existing instance, so that effectively "new" is ever called only once. Maybe I'm wrong here :) thanks for the point about including a cleaning fn in the singleton!
@@adicide9070 I’d go the simple way. Just create an instance and export it, probably as the default. I showed the instance version to give a couple of different options. When I redo this as a scripted video I’ll make the different options and which one I’d go with more clear.
@@jherr the expression fishing with dynamite comes to mind: D this was fun though. and visitor was so easy to miss. It'll be a blast to the edited vid and contrast, since I coded along and let myself experiment around it a bunch. I think I made a comment for a "factory function that wraps a class that's set up to produce a singleton" and then you export a single instance??? :D
Hey Jack, your tuts are awesome! Nonetheless here (around 37:43) I think you are using reduce the wrong way: isn't it meant to support a functional/immutable approach? (in fact, shouldn't the return value from reduce be used?) The way you are using it here is mutating the state and if mutability wasn't a problem you could have achieved the exact same result with a simpler forEach, mutating the external "found" property, don't you think?
You could definitely do it with a forEach, but I think it's basically still immutable. So these are the lines: github.com/jherr/ts-patterns/blob/master/src/index.ts#L96-L111 You could do something like this: return Object.values(this.db).reduce( ( f: { max: number; item: T | undefined; }, item ) => { const score = scoreStrategy(item); return score >= f.max ? { max: score, item: item, } : f; }, { max: 0, item: undefined, } ).item; Which would more clearly be immutable. But... it's harder to read. :)
@@jherr if you wanted to use an immutable/functional approach I would rather rewrite those lines like this (disclaimer: I admit I havent't tested the code): const found: { max: number item: T | undefined } = Object.values(this.db).reduce( (f, item) => { const score = scoreStrategy(item) if (score >= f.max) { return { max: score, item, } } // else return f }, { max: 0, item: undefined, } ) Otherwise, with mutability/forEach, like this: const found: { max: number item: T | undefined } = { max: 0, item: undefined, } Object.values(this.db).forEach(item => { const score = scoreStrategy(item) if (score >= found.max) { found.max = score found.item = item } }) ...which works as well and to me looks a bit simpler, without the complications of reduce :)
@@mettjus I guess the way I look at it, and the reason that I chose `reduce` is that I'm looking at it as an elaborate Math.max function where I don't want the max, but I do want the item with the max. So instead of just tracking the max, I track both the max and the max item and then just take the item. And since I'd use `reduce` if I wanted to hand write Math.max I figured I'd use `reduce` here since it was just a slight variation. Anyway, I think both approaches are absolutely fine.
Hier I am diving in your tutorials. I am java developer. An from one day to another the company want that I also continue the front end of another software. where I do the backend. Typescript and Angular was for me only to litle demos to run my python Projects. I am fullstack developer Java EE. And of course JS is a litle know for me. But The framework makes me crazy, the settings files, DI, yarn , tsconfig. Und you helpme to finall run an set yarn. Thanks!!!!
The first implementation is not a singleton. Every time createDatabase function is called, it returns a new instance. If that's how you want to implement a singleton, you can move the class into a separate file and export the instance itself, so every other file importing it will import the same instance
At 17:25 I wish you would have explained line 23 better. I’m not the strongest OOP programmer... i’ve never seen a class instantiate inside itself. And I don’t get how a static instance variable can hold a new object. It might be 00P 101, but Im missing it.
Yep, the class creates the single instance of itself because the constructor is private and can only be called by the class. This is the "classic OOP" way to do that and it's actually not the one I'd recommend and I'll clarify that in the scripted video.
@@DedicatedManagers There are a bunch of utility types: www.typescriptlang.org/docs/handbook/utility-types.html . They are very handy and I'll be covering more going forward.
When u presented factory pattern, u jump straight to singleton pattern, but the idea of factory pattern is to change implementation on the fly, but the factory function returns only one type of implementation... or at least present as abstract factory, with if else inside the function About visitor pattern, why u ommit the accept method? All this looks like semi way of introducing design patterns....
Definitely been bitten by DI and AOP, but not so much OO. That being said, at least in React/Typescript if we do OO we don't do it with deep inheritance like we do in C++/Java/C#/etc.
@@codezero6023 My original approach was more of an `on("beforeAdd", () => {})` type deal. And I got the external typing working well, but the internal typing wasn't great. Plus I liked the feel of the `effector` API so I re-did it this way, and as a nice side effect we have a cool little function that can make generic subscribe-ables.
@@whatthefunction9140 TBH, I'm not sure if I really understand monads the way an FP professor might, but I can tell you that in practice, these are just persistent closures. The most practical example is something a closure that has some data. function createCounter() { let count = 0; return { increment: () => count++, get: () => count }; }
Gotcha. It's a big more generics than I've been putting up on the channel so far. I'll do an intro video on generics. They are really important to getting the most out of Typescript. And they really improve the quality of your code. So definitely worth learning.
@@jherr I think that’s probably good. I’ve seen and done generics before, its just I’m not that strong in the area so your discussion was just too fast and missing the extra guidance I needed to understand what was happening (I’ve tried rewatching a couple times).