How to implement the Decorator Design Pattern in C#

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
welcome to another episode of design develop share my name is David Anderson and today we're going to talk about the decorator cut and we're gonna do some interesting things so the decorator pattern is actually probably my most favorite design pattern there is out of the game of form patterns maybe of all time and the decorator pattern has a very unique thing that it does for you in your program and so basically if you have the need to add behavior to objects at runtime and maybe many kinds of different behaviors and maybe aggregate them together in a composable way the decorator pattern is actually really great for that and usually you'll end up finding the need for the decorator pad pattern when you realize that you have the need for multiple inheritance or the an inheritance model just doesn't get you what you need and you can simulate multiple inheritance with interfaces but it's not the same thing and you can't share code and in the same way that you could otherwise so we're going to show you the decorator pattern and you know that's kind of the reason why you might use it but we'll make this make a little more sense so we're gonna switch over to visual studio we're gonna read some code from scratch show you basically how to implement this pattern and maybe come up with a good example as we go along cuz I don't have anything we're just doing this on the fly and by the way a good example of the decorator pattern is actually strings stream objects and dotnet I'm just gonna create a basic string here and yes I'm using my mechanical keyboard so it might be a little loud and I mean a dotnet core app so let's actually create you it's actually Kirti file string and it really doesn't matter for me to show this so we're just gonna go ahead and do this but if you've ever used any kind of string and let's say you've ever used the buffer the string you'll notice that it takes another string as a parameter that's one sign of the decorator pattern the second sign that you can look for is that buffered stream watch also derive from string so that is the probably one of the easiest ways to identify if you are actively using the decorator pattern on something that an API that might already exist is typically you'll have some type of object that you're using and you'll create some other very similar type and they both derive from the same base class stream and it'll take the more general stream or object as a parameter and basically what it's doing is its wrapping that stream object and because it derives from string that wrapper has the same public API service at its core now that my buffered stream obviously might add new properties and methods or behaviors and things that you might not even know are going on just by looking at all the surface but that's how easy is going to identify the decorator pattern so we're going to show you how to implement that in a few different ways I'm going to show you how to first implement the basic pattern without doing anything fancy just the bare-bones pattern and then I'm going to show you how to better implement that pattern in a way where you don't run into some of the gotchas with the pattern because the pattern can actually be fairly difficult to implement correctly especially when you're dealing with types that have private or even protected members the pattern can prove to be some challenging or questionable and said we're going to we're going to go talk about them so we're going to add first off we're going to add a customer class I'm just gonna a lot of people if you look on do factory or other places that have design patterns people like these pizza with pizza toppings or coffee with cream and milk and sugar and basically various ingredients I don't want to do that we're going to try to do something a little bit more concrete that people could relate to and something more concrete customers almost any software system has some kind of customer or user and like I said we're doing this on the fly so it really doesn't matter so we have a base class customer here that is sir and let's say our customer does something so customers typically have a name and we're going to create basic constructor for this as well else for a basically you give a customer name I do use resharper for those of you that have maybe are watching my videos for the first time so a lot of the keyboard shortcuts I have or the code generation will come from me using that I get a lot of questions about that okay so we have a name very straightforward let's also do something for example but we need something that we can act upon and change so let's let's say a customer has a balance that's a pretty easy easy thing to talk about and let's say that customer we can do something on that customer we want to be able to take some action on it have some kind of behavior so let's say set balance or actually this is you can call it purchase and we'll say a mountain who cares what we're purchasing this is just an example but let's say that the fundamental logic for this is that we're going to simply add the amount to our balance and that's what the purchase method does so extremely straightforward now if I were to want to create different kinds of customers I could derive from this and create a super type and so if I was to say so we're now we're going to expose the need for the decorator pattern so I'm gonna say public class bronze customer right so let's say I'm a Best Buy Awards member or something I'm a bronze customer and I derived from customer and I know that I'm gonna need to implement that base functionality by passing down the name and let's say that in that bronze customer I want to override the purchase behavior all right and so I can make that method virtual and I can override that method and let's say just for the hell of it a bronze member always whatever you're purchasing gets a $1 discount and we're not going to add any validation logic or anything like that because for the sake shown the pattern it's it's irrelevant we don't care so bronze customer does something different so let's go ahead and use those two things I'm gonna say Bob I guess and I'm gonna make a purchase for $50 and we're just going to print that to the console window so we're gonna print this to the window and let's go ahead and just write a new line there nothing special I'll run that and now you can see we got 50 so now let's say I want to be a bronze customer so now we have an inheritance situation here so I can't just take that customer object and turn them into a bronze customer I literally have to create a new customer object to do that and then copy over the property value so we're gonna basically do that and here again I have to literally copy that I can't just extend my original customer object and that's kind of that's kind of the thing I want to do but we're basically showing the initial attempt at inheritance so then I can say and maybe I need to also copy over that customers balance right so we need to do that first and then we're basically do the purchase through the bronze customer the same amount and I'll put that right and now we should get an output of 49 because we said that a bronze customer always gets a dollar discount so now we have a value of 49 so the problem then comes into if I want to add even more kinds of behaviors to this like maybe I have a bronze customer as a certain kind of attribute or I can assign to a customer as part of my a shopping app or my website but that maybe I also have something like a VIP customer maybe you can be both maybe you can have a bronze status and a VIP steps right and you can kind of start to see where this gets difficult we only have one property right now called balance and we have one method called purchase but as your customer object comes to a point where you have ten properties five methods and now you want to start tying all this logic together inheritance doesn't work really well because then you also run into situations that does your VIP customer derived from bronze customer or does it derive from customer does bronze customer derived from customer and maybe you want all these things to be combined and the inheritance model and c-sharp with multiple without multiple inheritance only really allows you to do one of those things not both so then there's the need for potentially the decorator pattern and again these are behaviors that we want to do at run time not a compile time inheritance is a compile time concept whereas the decorator pattern I'm writing all this code at run time which means I can also write conditional statements to check you know maybe if the customer has a zero dollar balance or a very low balance and maybe they have a really good history with the company they've made purchases for 10 years based on that criteria I might have something like you know age is greater than 10 years then I might associate them as a VIP customer and I want to be able to do that at run time so what we're going to do is actually implement the decorator pattern instead so now we've established some concept so the way to do this is it's still kind of done through inheritance the difference is instead of overriding the base purchase function and assuming that the balance will be passed in and the name will be passed in we're gonna actually pass in a generic customer and this can be any customer or anything that derives from customer and what we're gonna do is add that as a private field we do need to pass still that customers name just because the base class constructor is going to require that and we'll get into a few things here in just a second and then what we're going to do is we're to decorate a customer there now what's happening here is this balance property on the bronze customer still is not the original balance property of this guy and so the decorator pattern basically we need to be able to override the balance property and redirect that's getting set methods to the decorated customers method and that's what kind of creates it as a wrapper so we need to make balance virtual we also need to make named virtual because we need to do the same thing so when you're using the decorator pattern it's very common that your I need all these to be virtual including the method we already had that and what we're gonna do is then override balance and what we're gonna do here instead is actually get the decorated customer balance and set the decorator customer balance so again we're redirecting the bronze customer getting set to the customer passed in and we're gonna do the same thing for me it's very broad name if I were to just say getting set and not do that basically I would be having two distinct values of balance I would have the balance of the Bronk Bronk customer and then I would still have a separate balance for the original customer passed in and we really just want one balance and in our purchase method we want to manipulate that original customers balance but just in a different way so what we're gonna actually do is instead of saying based on purchase which again would actually indicate we're going to have a separate copy balance we're going to actually say decorate a customer purchase amount minus one and so the thing you need to realize about the decorator pattern is you're not trying to modify or override private implementation details of the original customer or even protected details what you're trying to do is add additional behaviors in the order you want to add agree to an already existing public API and you're not even trying to change that public API you want the original public API to work exactly as intended you just want to do something else or manipulate it very minimally I don't want to override it and change the implementation I still want that to work so the coffee example that a lot of people use you still want to start with a black coffee I just want to add sugar to it but I don't want to change the way that coffee was brewed or how it was made or how much coffee was produced in my coffee cup 6 8 10 ounces but I might want to add some sugar to it so I'm not trying to change the original implementation I'm just doing something a little bit different let's still utilize so the effect of that now is if we come back to here let's go ahead and erase that and let's set our customer balance well let's just leave that at zero because we're gonna make a purchase again so we know their balance starts off at zero so now I'm going to say bronze customer except this time we're gonna pass in the actual customer object okay and we're gonna operate on this customer I could still make the purchase as just a regular customer and make a $50 purchase and their balance will still be $50 instead I'm gonna operate on that same customer as a bronze customer and do the purchase that way and we're gonna give it the same exact value of 50 right but now you're gonna see something interesting I'm gonna actually come down here and change the output from bronze customer to customer and we're gonna go ahead and run this and what you're going to see is our output if I did this correctly so we actually need to you very very right so you have to be very careful this so we do have to change the implementation of the base constructor we have a vertical because we made that virtual this is going to call the overridden version of name and so we actually don't want that what we're gonna do instead is create a private field to do this work I'm gonna say this name equals name and then we're going to come in here and make the base class property store that in a field so that way we're not accessing a virtual number in the base class so you got to be very careful with that we could even potentially do the same thing with balance but in the case of balance we're not calling it from the constructor so there's no that issues not there and it will have a private field that that gets stored and so when we say base customer name here though that'll solve that problem the base class will in fact store the name in the variable but the overridden method will work off the decorated object which always still that same field at the end of the day so we're gonna go and run that now and now what you can see is our output is still 49 right which is what we wanted now what we can do is set a breakpoint on line 17 and run this again just so we can observe what the decorator pattern is actually doing so we had a customer with a balance of zero and then we created a bronze customer to decorate that customer so we're still working with the david customer so if i actually look at bronze customer my name is david and the balance is 49 and if i look at customer we should see that the balance is 49 that's the coolest thing that the decorator does for you now what we can do as we can come in here and we can start adding all kinds of different things so i can now add a VIP customer and again we're just going to operate on the base customer class we're gonna actually just copy all this from our bronze customer it's basically a very similar template you always want to work off the base most class if you can unless there's a reason for you not to maybe you need to decorate a more derived version of customer so in this case we're not gonna really change much of anything but let's say for a VIP customer yeah their balance or when they purchase something we're gonna be something different maybe they get an additional four dollars off and so let's say the idea is if you're a VIP customer that also means just for the hell of it that you'll are a bronze customer so you go from bronze - VIP and maybe when you become a VIP you still get the benefits from the bronze tier but in addition to the VIP tier so we basically are saying in that case we still want that $1 discount to apply in addition to the four for total of five dollars for a discount now there are some things we can do and maybe we want the flexibility to say that not all VIPs are bronze customers right maybe there's other circumstances in the system so to show the cool thing the flexibility of the decorator pattern let's go ahead and create the VIP and I can pass in my bronze customer right and then we're actually gonna say VIP customer purchase $50 and let's go ahead and run that and now we should see we have a balance of 45 so we had the $1 discount that applied we had the four dollar discount that applied now what if I don't want in my shopping cart app or whatever my store maybe I don't want a VIP customer maybe there are certain circumstances where they don't get both bronze customer benefits and VIP customer benefits maybe at one point in time that was an option and there people are grandfathered in to that situation but maybe now too bad so sad you only get the $4 discount no bronze benefits because maybe the business decided that's too much so we're in this case we're just gonna pass in the regular customer instead of the bronze customer and we're going to make that purchase and we should have the value 46 so we didn't have the benefits of the bronze package deployed and so you can see that I could have passed in the bronze customer or just a normal customer so the decorator pattern you're seeing how we can implement something as powerful as inheritance but more powerful in the sense that I can combine many different behaviors together so I don't have the option of multiple inheritance but I can also write at run time a lot of if conditions or logic to determine what kind of decorations I apply it to my objects so that's the really cool thing about the decorator part I found the decorator pound is really nice for business layer classes where you need to have lots of different behavior for Business Objects and inheritance just becomes too complex to manage those behaviors and again it applies to both properties and methods basically the things you can override so that's really cool but there are some challenges that come with the decorator pattern and let's actually talk about this so let's say let's say for instance that there is a protected member on the decorator object so let's create a protective member and let's call it I'm gonna create this is virtual and let's call this clear balance and let's say that we just set it to zero straightforward so that's a protected member and we're actually let's actually make sure that's on the base class so all the way down to customer now I can from within this base class access that clear balance method right there's nothing wrong with that and that'll clear the balance out to zero like I want and if this was just deriving from customer and I was not using the decorator pattern that wouldn't be a problem but the problem that the decorator pattern has that makes it difficult to know should I override this and redirect it to the decorative object or can i or maybe you want to and you can so if I go in or a bronze customer I can override that function but because it's a protected member I can't access that on the object I'm actually trying to decorate it's protected so I can access the base glass one but that doesn't do anything because we're not trying to we're not in the traditional inheritance model we're trying to work off the already existing public api's and values of the object for decorating so customer so maybe I don't even intend on clear balance being a extendable method but the problem is I can access it I can override it and when you have decorator pattern you have again five ten properties or methods it's not then clear which ones you should override which ones you can override and there might be situations where you don't have control over that API and maybe you want to decorate it but too bad it's not public you can so there's a way to kind of solve that to make your API a lot more clear and that's to actually use an interface now this only really works really well when you actually own the code for the base class and maybe it's a design choice for this to be protected intentionally you don't want people to access that as a public API so that's fine but you don't want people to be confused when they're decorating that object either right you don't even want them to care that that's there so the best thing you can do to improve that shortcoming of the decorator pattern and you'll see a lot of places recommend this is to use an interface so what we're gonna do instead is extract an interface and I'm actually good what I like to do is actually call this as it is I customer or you can actually call it a decorator if you want and you get a multiple interfaces maybe you have an eye customer interface and an eye customer decorator interface but let's just call a site customer because we don't really want to name our classes after the pattern we that's usually a code smell but uh so let's basically we have a name right so actually I'll do that so we have a name we have a balance that's we have a purchase method right at the end amount and we can go ahead and point that without any coding changes to our base class and because of bronze customer derives from that it'll implicitly have to implement that interface as well but the thing we're going to do differently now in our decorator pattern is instead of taking the concrete object as a constructor parameter we're going to take the interface and so the interface is basically now we've clearly defined only the things that we can decorate and we also we're going to derive from the interface instead of the concrete class so now the thing that we get to do is we don't actually have to override those methods we can just implement them but now we don't have access at all to the clear balance method which is great so now our decorated object is much more clear in its intention and I still can't access that which is great so we've basically reduced the API surface to only what we want to be able to be decorated and again in this case there's no base class constructor which is actually even better because now it's not so confusing as whether we're utilizing base class functionality versus the decorative functionality so that kind of solves that problem so now we have a really nice clean decorator object and we would basically do the same thing for the VIP customer because we don't want any of that confusion there either and again we can get rid of the base class constructor and now essentially we are only exposing the public API and our decorator objects we don't have to worry about any base class functionality because that just makes it too confusing so we still get the same code over here it doesn't change any of this at all so if we were to run all this we're gonna get the same behavior we'd expect our VIP customer that decorates our normal customer gives us a balance of 46 on a $50 purchase or I can revert it back or we do that on a bronze customer and run that and we should get a $45 balance so we didn't lose anything we gained simplicity the one thing I guess you could say we lost is complexity so my recommendation with the decorator pattern is pretty much to always use an interface unless your base class doesn't have any of those concerns but often times you'll find that you end up having those concerns whether you like them or not so it's best just to have an interface anyway you do end up with a lot of class interface pairs so you know customer and eye customer but there's nothing wrong with that I have projects where we've got 50 60 objects the 30 of them are the actual concrete types and the other 30 year interfaces there's nothing wrong with that it makes your code base much more understandable now there still is the key so if I still might want to derive objects from customer and not use the decorator pattern in conjunction with using the decorator and that's the cool thing about this is you're not forced into any one scenario you don't you're not forced into inheritance and you're not forced in a decorating pattern and they both work well together so if Braun's customer was just a traditional class that derived from the customer supertype I could still decorate it with the VIP customer and there's there's no issues so you can mix and match so you can kind of see the value that pattern so that's the decorated pattern I hope you've enjoyed that video it might have been a little long-winded but modifying objects by adding an extending behavior with other behaviors at runtime that's the key that's why you want to use the decorator pattern when inheritance just doesn't do it or inheritance the model becomes too complex for your needs the decorator pattern is a very cool thing in your toolbox it's not a an end-all solution and you can do some crazy things that you can make your codebase very complex with the decorator pattern but when used right it is probably one of the most valuable patterns that exists out there in my mind and it's also why it's one of my favorites I've created an entire business layer for an enterprise app with an extensibility model based on the decorator pattern and that extensibility model was basically we have a code base and with the customer object for instance and that customer object needs all kinds of things that can happen to it based on feature modules so we have feature module ABCDEF in this enterprise system and customers kind of the core concept of this system and I don't want all the code from feature module ABCDE F and that that base class and I also don't want at some point inheritance model doesn't work I can't necessarily feature modules are often horizontal and not vertical meaning I can't necessarily derive customer object in feature module F from Eve from D from c b8 i that doesn't work not all customers might have all future modules and i might need to make some match depending on what I'm doing in the application so the decorator pattern allows you to solve that kind of problem and so we used it when I designed that business layer especially in terms of future modules it was a really cool thing it also allows you to only pull in the code that you care about depending on what you do so I could write some code to say if the customer has this module then I know it's safe to use the decorator objects on my base customer class if they don't have the feature module I know I don't have to use those objects but if all that was baked in to an inheritance model or in that base class that gets really hard other ways you could solve it or by not using the decorator pattern or not using inheritance but by using lots of other business objects or manager objects or service objects that tie all that business logic together but I found the decorator pattern is more of a natural fit for that where I truly want like a customer and then I want an audited customer with audit tracking for example and I don't necessarily ye and so what I want to happen is I want audit tracking to occur whenever I perform actions on that customer object I don't necessarily have to want to necessarily manage a whole bunch of other audit objects and things like that I just I want to call customer not purchase and then make an audit and I want that to happen automatically just through the decorated object whether based on whether they have the future modular so that's the decorative pattern if you liked this video and you found it helpful definitely remember to like the video below hit subscribe shirt and we'll see you next time
Info
Channel: DCOM Engineering, LLC
Views: 1,571
Rating: undefined out of 5
Keywords: decorator pattern c#, decorator design pattern, decorator design pattern c#, decorator pattern, c# decorator pattern, c# decorator design pattern, design patterns c#, c# design patterns, gang of four c#, gang of four design patterns, gang of four design patterns c#, decorator c#, c# decorator, stream c#, c# stream, stringbuilder, stringbuilder c#, softwa, design patterns, c#, .net framework, csharp, code, coding, software development
Id: 2Q36vqdaRr8
Channel Id: undefined
Length: 35min 54sec (2154 seconds)
Published: Sun Jun 23 2019
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.