I made a follow up to this showing how we can extend this to send notifications based on a users preference. Check it out here th-cam.com/video/aBOrVRKK3fA/w-d-xo.html
honestly, these patterns....once you have written enough software you just refer them all as "abstraction" and you just know how much or how little based purely by feel, business circumstances, team skill, tech stack maturity, product maturity etc etc.... i think attempting to give labels trivalise things which invites devs just slapping on random ones instead of engaging a brain cell. In other words, design patterns was an attempt to short-cut having experience.
@@blipojones2114 design patterns was supposed to be a communication tool. So if I say "we can use a strategy pattern here", we should both be at ~90% understanding of what that means. Without having to describe the whole abstraction. The problem is when people started using a communication tool as a bible.
@@blipojones2114 Math formulas are an attempt to short-cut having experience. See how that doesn't work in other fields? Design patterns are the same way, they are defined ways of doing a certain thing.
@@CottidaeSEA I do not agree that a mathematical formula and a software design pattern are comparable. Formulas give the same reliable results, software pattens don't.
it added an abstraction layer, everything is happening within that method containing the switch statement, it's just fine cuz the service where the business logic happens does not need to worry about the instanciation of the OrderNotifier type
But couldn't I have just extracted the switch statement logic into individual methods? Clean code fans vs monolith class enjoyers But seriously lately I have been bothered about how convoluted it becomes to trace code after adopting proper architecture patterns. Maintainability is easier in some ways but also harder in other ways 😓
Extracting the code into methods still couples the code to the class. So everytime you want to add/remove an implementation of a notifier, you'll have to modify the Order Service. The switch statement is just an example, but the dependency can be injected for the class in many different ways. So a notifier can be implemented without touching the OrderService class. It makes testing much easier too. The layers of abstraction can get ridiculous when implementing design patterns liberally. This is where a senior/tech lead with experience should help guide to make sure things don't get out of hand.
@@darylphuah also some of these methods might need their own private methods in the future if they get more complex, so it's better to have their own services so you can keep logic of the sms in the sms service.
@@pitchwaiz yeah that's true but on first writing, sometimes it's easier to keep it all in a single class / function. Clean code wants us to almost anticipate future complexity that might never arrive, and in doing so brings its own overhead. My feeling would be the if else statements are more readable, not running up chains to see what actually happens. If it gets really complex, then yeah, abstract up a layer, but I guess... not prematurely.
@@jordixboy switch statements are usually safer as in case you add additional enum value, it force you to add a case for that new value - you don't get that with if statements
Brother, Please cover all design patterns in these short videos please. It is just the world's best video to revise a pattern or to get a quick intro if you are new to them.
The pattern itself is fine, but the example has a problem in my opinion. An “EmailService” or an “SMSService” should have limited responsibility. The “EmailService” shouldn’t know how to send an order confirmation. This can be the responsibility of a class that implements the interface and uses the “EmailService”. By using composition, your “EmailService” will stay simple and be more portable between implementations.
What if you need to use the email service twice and thus send the confirmation twice as well, you'll need to create an additional class that just does that. Isn't it too much?
@@grenadier4702 I don’t think there’s necessarily one perfect solution. As your platform grows you might first group functionality a certain way and change that over time. I think that people sometimes are worried to get it 100% right from the start. You could have an “OrderService” that can use the “EmailService” and “SMSService” to send order confirmations. It seems likely that you’ll want to send more types of emails in the future. I recommend designing it in a way where you could port certain classes to other apps. If you need Email or SMS functionality in another app, those classes would be a lot more portable if they don’t focus on the actual content that they are sending.
I love using the strategy pattern with higher order functions: no need to create an interface and classes that implement it. Just create function or directly inject anonymous ones👌
@@jonowilliams26 probably nicer then to perhaps introduce an OrderManager that holds on to that internal collection of providers? Kinda how the logging is implemented maybe? #thought-out-loud
This is the same thing as the delegate pattern although you could make an argument that delegates have more methods and/or are long lived but still the same imo. Either way this becomes extremely powerful when using mock strategies/delegates for testing.
Dependency Injection. In the Project or Startup class in ConfigureServices he is setting which concrete class to use whenever a class takes a IOrderNotifier parameter. The concrete implementation is set in the configuration file. .NETs dependency injection will work behind the scenes after you register a service to give you the implementation later on
I often like this approach. Higher abstractions only when the need has been proven, unless its clearly a part of the requirements. I come from JS and a lot of enterprise software terminology has been bloated. Di, strategy, adaptor etc. A function is good precisely because it has a lot of those concepts baked into it at a fundamental level.
I like the way you handle DI. Instead of registering each one the scan through the interface and register it via DI and in the calling class I inject Ienumerable
Would have liked to see an runtime implemention / usage of this examples, because the example u gave was using the Startup of the app to choose one of the implementations, but most times I feel like u would use this in runtime with some kind of Resolver to get the wanted implementation of the Interface. Still very well explained, my favorite pattern as well
If you the implementation can vary in runtime maybe depending on the custom for example one approach could be using the factory pattern. So you inject the factory into the OrderService which is responsible for instantiating the appropriate implementation
@@dumax97 yeah, no problem with that. The factory will centralize the logic for creating the objects. How they are created is another story, using a switch is not wrong, a hashmap or other techniques are possible as well
My professor introduced this pattern, but had written several Java files to implement it with complex relationships. I implemented it functionally in 7 lines of code. This is one reason why I really really hate OOP related crap.
This logic is nice when we have that distinction and comptime. But when there's a need to use customer provided means of communication (whether or not customer added phone and accepted sms notification, added email and accepted email notification and so on) this logic basically will be extracted in another class with same if-else statements? Do we have a pattern for such usecase? Or we will be making more abstractions with something like "IOrderNotificationPossibility.CanAccept(NotificationType.Sms, user)" in order to make it look seamless and easy to extend?
You could use the factory pattern along side the strategy pattern. The factory takes in a customer/user which creates the order notifier strategy to be used
@@jonowilliams26 hi! Thank you for an answer. So it basically means that this "if-else\switch" related to some users properties would be located inside this factory?
My way of doing this is to have a function parameter maybe called "extras" that you can pass in additional information. The service itself will then know how to handle the "extras". That way you can still use the same pattern.
It's my favorite pattern, but usually I has to be combined with another logic and/or pattern to be the most useful. For example I used it in a validator class. There I created an interface for validation rules. They have one "isApplicable" method that accepts the validation rules of the current request and return a boolean, and a "validate" method, that return a validation response with an optional error message bag. The validator has a method for registering validation rules. Whenever it receives a request, it will then loop through all registered validation rules, validate the request for each one that is applicable for it and collect all error messages. Adding new validation rules is very easy and you don't have to touch any existing code apart from the place where the rules are registered. Not a single "switch" used anywhere :)
From my perspective, this is complexity-galore added to solve some problem that could be solved with a simple switch-statement. To me, a lot of OOP is just adding layers of complexity, making the code harder to debug and reason about.
That carrier pigeon notification though is now going to be sent to everybody because of that one client. Clearly there's a flaw in the logic. It needs to be personalized ;)
You're just moving the conditional elsewhere in this case switch statement... People often misuse and abuse patterns/architectures, they have there place yes, but the problem with a lot of engineers is precisely that, over-engineering, having a hammer and seeing needles everywhere, you're opening clearly demonstrates that by saying "this is my favourite pattern", we should not have favourite "things" this doesn't make us good engineers imho. This only leads to fanboys applying shitty solution to problems, like javascript on the desktop lol
see this is where these software principles break down. you are still doing the switch statement (basically the if statement) in another place now. this is the problem with clean code, uncle bob and design patterns. has made everything messy by hiding different things in different places. dont follow these please. take it from a software dev with over 10 yrs of experience, what you want to do is write simple imperative code with re usability through functions and that is it. if you are an intermediate dev it feels very good to learn these new things, (i went through them myself) but once you gain even more experience you realize this is all bs. code should be as easy to follow as possible. look at how game devs do things. when there is no space to waste time on useless cycles due to real performance reasons, the code becomes much more simpler and streamlined. anyways, reply to my comment if you want me to clarify something further.
This is almost good. This has a significant performance overhead, it's much better to send in a function pointer or a closure instead of an interface with no downsides.
@@liquidpebbles You don't need a class that implements a method called through a vtable. You can just pass the function directly. It's often clearer (just a function doing what you want or a closure which can be just declared right there) and faster (literally a function call). I'm all for strategy pattern and dependency inversion, I think it's good but OOP tends to make it more convoluted than it needs to be.
@@CamaradaArdi Finally someone sees logic and simplicity. This pattern is literally solvable by higher order functions. Just pass a function as an argument. The argument would be the strategy it employs.
I made a follow up to this showing how we can extend this to send notifications based on a users preference. Check it out here th-cam.com/video/aBOrVRKK3fA/w-d-xo.html
I didn't know this was called the strategy pattern, to me its just an example of why and how interfaces are useful lol
honestly, these patterns....once you have written enough software you just refer them all as "abstraction" and you just know how much or how little based purely by feel, business circumstances, team skill, tech stack maturity, product maturity etc etc....
i think attempting to give labels trivalise things which invites devs just slapping on random ones instead of engaging a brain cell.
In other words, design patterns was an attempt to short-cut having experience.
@@blipojones2114 design patterns was supposed to be a communication tool. So if I say "we can use a strategy pattern here", we should both be at ~90% understanding of what that means. Without having to describe the whole abstraction.
The problem is when people started using a communication tool as a bible.
Totally right sir ! The ‘pattern’ is that there is no pattern !
(Also remember that encapsulation is more important than polymorphism in OOP) ;)
@@blipojones2114 Math formulas are an attempt to short-cut having experience. See how that doesn't work in other fields? Design patterns are the same way, they are defined ways of doing a certain thing.
@@CottidaeSEA I do not agree that a mathematical formula and a software design pattern are comparable.
Formulas give the same reliable results, software pattens don't.
it added an abstraction layer, everything is happening within that method containing the switch statement, it's just fine cuz the service where the business logic happens does not need to worry about the instanciation of the OrderNotifier type
But couldn't I have just extracted the switch statement logic into individual methods?
Clean code fans vs monolith class enjoyers
But seriously lately I have been bothered about how convoluted it becomes to trace code after adopting proper architecture patterns. Maintainability is easier in some ways but also harder in other ways 😓
Extracting the code into methods still couples the code to the class. So everytime you want to add/remove an implementation of a notifier, you'll have to modify the Order Service. The switch statement is just an example, but the dependency can be injected for the class in many different ways. So a notifier can be implemented without touching the OrderService class. It makes testing much easier too.
The layers of abstraction can get ridiculous when implementing design patterns liberally. This is where a senior/tech lead with experience should help guide to make sure things don't get out of hand.
Abstractions add complexity. Avoid not needed abstractions. Your idea looks good and IMO may work in mentioned scenario.
@@darylphuah also some of these methods might need their own private methods in the future if they get more complex, so it's better to have their own services so you can keep logic of the sms in the sms service.
FP would solve this, but that is too simple. If you aren't making things convoluted, you aren't a "real programmer" as they say.
@@pitchwaiz yeah that's true but on first writing, sometimes it's easier to keep it all in a single class / function. Clean code wants us to almost anticipate future complexity that might never arrive, and in doing so brings its own overhead. My feeling would be the if else statements are more readable, not running up chains to see what actually happens. If it gets really complex, then yeah, abstract up a layer, but I guess... not prematurely.
It's all about interchangeability. Also, those are pretty cool animations it helps to get a better grasp on the examples
I see a switch still! It's just in the DI.
You can't always do this if you need to decide based on the request/order/user-preferences
you sound like a senior, not like the one from the video, lol.
Exactly, and even if this notification settings were user settings then you'd have to restart the client in order to take effect.
There has to be at least one switch to support multiple implementations unless you want your DI to choose what implementation to pick
@@dumax97 do you get that the switch statement is the same as the if, and you are just moving it somewhere else?
@@jordixboy switch statements are usually safer as in case you add additional enum value, it force you to add a case for that new value - you don't get that with if statements
The interpreter pattern is such a good pattern you can implement every pattern, including itself, in it
Tagless Final the ultimate one 😎
Means that we have to create a new class for a new NotificationType right?
Brother, Please cover all design patterns in these short videos please. It is just the world's best video to revise a pattern or to get a quick intro if you are new to them.
What do you do if a customer wants to receive both email and SMS notifications? What is the best way to deal with that?
I think this is definitely a good approach, however if I'm not mistaken it only allows for one type of notification or am I mistaken?
Please can you tell me which software you used to create the video?
The pattern itself is fine, but the example has a problem in my opinion.
An “EmailService” or an “SMSService” should have limited responsibility. The “EmailService” shouldn’t know how to send an order confirmation. This can be the responsibility of a class that implements the interface and uses the “EmailService”. By using composition, your “EmailService” will stay simple and be more portable between implementations.
What if you need to use the email service twice and thus send the confirmation twice as well, you'll need to create an additional class that just does that. Isn't it too much?
@@grenadier4702 I don’t think there’s necessarily one perfect solution. As your platform grows you might first group functionality a certain way and change that over time. I think that people sometimes are worried to get it 100% right from the start.
You could have an “OrderService” that can use the “EmailService” and “SMSService” to send order confirmations.
It seems likely that you’ll want to send more types of emails in the future. I recommend designing it in a way where you could port certain classes to other apps. If you need Email or SMS functionality in another app, those classes would be a lot more portable if they don’t focus on the actual content that they are sending.
I love using the strategy pattern with higher order functions: no need to create an interface and classes that implement it. Just create function or directly inject anonymous ones👌
It is also great for finding different calculation strategies
What if we must send the notification through all the providers (including the pigeon 😁) ?
Rather than injecting a single IOrderNotifier, inject an IEnumerable and loop through and call each provider
@@jonowilliams26 probably nicer then to perhaps introduce an OrderManager that holds on to that internal collection of providers? Kinda how the logging is implemented maybe? #thought-out-loud
It's dependency inversion?
This is the same thing as the delegate pattern although you could make an argument that delegates have more methods and/or are long lived but still the same imo. Either way this becomes extremely powerful when using mock strategies/delegates for testing.
I does OrderService know which registered implementation to use?
Dependency Injection. In the Project or Startup class in ConfigureServices he is setting which concrete class to use whenever a class takes a IOrderNotifier parameter. The concrete implementation is set in the configuration file. .NETs dependency injection will work behind the scenes after you register a service to give you the implementation later on
Hello! What do you use to create these stunning code presentation videos
How is this different from just using a function that takes an order and does the notification thing?
I often like this approach. Higher abstractions only when the need has been proven, unless its clearly a part of the requirements.
I come from JS and a lot of enterprise software terminology has been bloated. Di, strategy, adaptor etc. A function is good precisely because it has a lot of those concepts baked into it at a fundamental level.
I use this pattern with a factory pattern!
I do too! It’s really good especially when you need to create a strategy based on some runtime variables
I like the way you handle DI. Instead of registering each one the scan through the interface and register it via DI and in the calling class I inject Ienumerable
Would have liked to see an runtime implemention / usage of this examples, because the example u gave was using the Startup of the app to choose one of the implementations, but most times I feel like u would use this in runtime with some kind of Resolver to get the wanted implementation of the Interface.
Still very well explained, my favorite pattern as well
If you the implementation can vary in runtime maybe depending on the custom for example one approach could be using the factory pattern. So you inject the factory into the OrderService which is responsible for instantiating the appropriate implementation
In that factory, there again will be the same switch
@@dumax97 yeah, no problem with that. The factory will centralize the logic for creating the objects. How they are created is another story, using a switch is not wrong, a hashmap or other techniques are possible as well
watch as this if stack (@0:36) gets ✨COMPLETELY TRANSFORMED ✨into a switch statement (@2:05)
This is surprisingly common thing to do. I didn't know it was called Strategy. Isn't this what interfaces are about?
Yep, it’s really simple and like you said, it’s common practice
good video plz make more content like this.
Thank you!
This is great until you try and figure out which implementation is actually being used... Plus if you want multiple notifies it's now annoying
or you could just pass a closure ¯\_(ツ)_/¯
You wouldn't even need to do that. Just a regular function call when you get the order. Strategies are OOP coping hard
I've been doing this since I started into code, I thought that it was the motive of the existence of functions
Nice one. A strategy factory would be nice then you just have a new registration for each type and you're done. OCP FTW.
I'm confused, it was like dependency invertion
My professor introduced this pattern, but had written several Java files to implement it with complex relationships.
I implemented it functionally in 7 lines of code.
This is one reason why I really really hate OOP related crap.
excellent explanation Jono. Short and as we said in colomba "Al grano"
Thank you!
This logic is nice when we have that distinction and comptime. But when there's a need to use customer provided means of communication (whether or not customer added phone and accepted sms notification, added email and accepted email notification and so on) this logic basically will be extracted in another class with same if-else statements? Do we have a pattern for such usecase? Or we will be making more abstractions with something like "IOrderNotificationPossibility.CanAccept(NotificationType.Sms, user)" in order to make it look seamless and easy to extend?
You could use the factory pattern along side the strategy pattern. The factory takes in a customer/user which creates the order notifier strategy to be used
@@jonowilliams26 hi! Thank you for an answer. So it basically means that this "if-else\switch" related to some users properties would be located inside this factory?
@@ilyahryapko yeah that’s right
My way of doing this is to have a function parameter maybe called "extras" that you can pass in additional information.
The service itself will then know how to handle the "extras". That way you can still use the same pattern.
It's my favorite pattern, but usually I has to be combined with another logic and/or pattern to be the most useful.
For example I used it in a validator class.
There I created an interface for validation rules. They have one "isApplicable" method that accepts the validation rules of the current request and return a boolean, and a "validate" method, that return a validation response with an optional error message bag.
The validator has a method for registering validation rules.
Whenever it receives a request, it will then loop through all registered validation rules, validate the request for each one that is applicable for it and collect all error messages.
Adding new validation rules is very easy and you don't have to touch any existing code apart from the place where the rules are registered.
Not a single "switch" used anywhere :)
From my perspective, this is complexity-galore added to solve some problem that could be solved with a simple switch-statement. To me, a lot of OOP is just adding layers of complexity, making the code harder to debug and reason about.
Absolutely correct. Functional Programming is the way to go.
I Agree but it's required for my assignments lol
That carrier pigeon notification though is now going to be sent to everybody because of that one client. Clearly there's a flaw in the logic. It needs to be personalized ;)
Good old polymorphism!
Why it looks like factory pattern to me 😬😬😬😬
Reminds me of Duck Typing.
Not sure it’s a strategy pattern
I thought it's a video from prnhub
You're just moving the conditional elsewhere in this case switch statement... People often misuse and abuse patterns/architectures, they have there place yes, but the problem with a lot of engineers is precisely that, over-engineering, having a hammer and seeing needles everywhere, you're opening clearly demonstrates that by saying "this is my favourite pattern", we should not have favourite "things" this doesn't make us good engineers imho. This only leads to fanboys applying shitty solution to problems, like javascript on the desktop lol
Julia has this thing called "multiple dispatch" that basically does what you're doing but within the language.
Almost every single language has this feature in one way or another
So the strategy pattern obeys the open-closed principle then.
see this is where these software principles break down. you are still doing the switch statement (basically the if statement) in another place now. this is the problem with clean code, uncle bob and design patterns. has made everything messy by hiding different things in different places.
dont follow these please. take it from a software dev with over 10 yrs of experience, what you want to do is write simple imperative code with re usability through functions and that is it.
if you are an intermediate dev it feels very good to learn these new things, (i went through them myself) but once you gain even more experience you realize this is all bs.
code should be as easy to follow as possible. look at how game devs do things. when there is no space to waste time on useless cycles due to real performance reasons, the code becomes much more simpler and streamlined.
anyways, reply to my comment if you want me to clarify something further.
bruh, i give up lol, idk why I find this shit so complicated
8000 lines of code and 50 files to avoid a switch statement.
man I hate enterprise code like this
This is almost good. This has a significant performance overhead, it's much better to send in a function pointer or a closure instead of an interface with no downsides.
What a ridiculous comment.
A closure is a great idea 👍
Care to explain?
@@liquidpebbles You don't need a class that implements a method called through a vtable.
You can just pass the function directly. It's often clearer (just a function doing what you want or a closure which can be just declared right there) and faster (literally a function call).
I'm all for strategy pattern and dependency inversion, I think it's good but OOP tends to make it more convoluted than it needs to be.
@@CamaradaArdi Finally someone sees logic and simplicity. This pattern is literally solvable by higher order functions. Just pass a function as an argument. The argument would be the strategy it employs.
Remember kids: the best pattern is a function which gets other functions as an argument
Cool video, nice animations, excelent explanation .. god how I hate Java-ish verbosity and it's OOP style 🤢🤢😅
LOL…just moved the if’s somewhere else.
Ofcourse, in the end the same code has to run. But the idea behind refactoring is to make it readable/maintainable/scalable
What if I want only two or three types of notifications out of the 5 you ended up with at the end?
Ughhh classes...
meanwhile in JavaScript:
const shipOrder = {
'email': () => ...
'sms': () => ...
'push': () => ...
}
shipOrder['email']()
Meanwhile in JavaScript:
let x = [2, 10, 1]
x.sort()
// result = [1, 10, 2]