- Overview
- Transcript
3.5 Decorator
Often we are deep into the development process when we realize that we need additional functionality in certain types. Instead of going all the way back to square one and adding the functionality to the base class and all the inherited types, we can instead create a new "wrapper" class that contains the additional functionality without changing any of the base type characteristics.
1.Introduction2 lessons, 04:27
1.1Introduction01:39
1.2Prerequisites02:48
2.Creational Patterns5 lessons, 52:47
2.1Factory10:16
2.2Abstract Factory12:24
2.3Singleton09:09
2.4Prototype09:18
2.5Builder11:40
3.Structural Patterns7 lessons, 1:05:54
3.1Adapter10:07
3.2Flyweight10:33
3.3Proxy05:53
3.4Bridge10:35
3.5Decorator11:44
3.6Composite09:43
3.7Facade07:19
4.Behavioral Patterns9 lessons, 1:25:42
4.1Iterator09:32
4.2Command07:48
4.3Chain of Responsibility13:47
4.4Mediator08:16
4.5Memento08:53
4.6Interpreter14:31
4.7Observer08:58
4.8Strategy07:36
4.9State06:21
5.Conclusion1 lesson, 02:42
5.1Conclusion02:42
3.5 Decorator
Now it's time to move on to our next structural pattern and that one is going to be the decorator. Now the decorator is a very interesting pattern, in that it's another way for us to provide structure, yes, but also to augment existing functionality that we find within our code with some additional functionality. While still all maintaining the same protocol and the same interface that we have defined at our most basic implementations of our classes. So let me show you how this is going to work. So we're going to create a new decorator pattern playground here. And let's start to define a simple scenario. Let's say that we are starting to build an application that can maybe build and design, and charge for some sort of food. Maybe I'm starting up a restaurant or something like that, and I need some sort of software that's going to be able to build and design, and charge for my products. So the first product that I wanna be able to create, that I'm very interested in trying to get started, is going to be some sort of a burger. It's gonna be a very simple thing, it's not going to be very complicated. We're just gonna try to focus on high quality and get a number of customers through the door, so that they can enjoy it as much as possible. So let's say our beginning implementation looks a little bit something like this. So our initial product is a burger, so we're gonna have a burgerproduct protocol. It's going to have two properties, a price and ingredient. And it's gonna have two functions, it's gonna have getCost and getIngredient. So that's gonna be our basic protocol that we wanna design around. Then, once I start building these burgers, this is what the basic is going to look at. This is gonna be the basic model. We're going to have a price of $3, the main ingredient is going to be beef. And then I can use my getCost and getIngredient functions to be able to retrieve that information. So it's nothing very complicated. But as soon as I start to do very well and I get a lot of customers through the door, they're gonna wanna start making alterations. They're gonna wanna say, I wanna add cheese, I wanna add lettuce, I wanna add tomato. I wanna add all sorts of different ingredients to this burger. And now all of a sudden, I have to be able to support those things. So how would I support that? Well, there's a couple of different ways that I could do that. Probably one of the first things that you're gonna think of is, well, I could just start to add additional properties and functions to my existing burgerproduct protocol, as well as my burger class. And sure, I could do things like come here and say maybe Var contains cheese, maybe that's a bouillon, and I could do the getter and the setter. And that's great, I mean, I could do that and every time I needed to add some sort of support for an ingredient, I could do that. It would work, but the problem you're quickly going to run into is, I'm gonna start to find that my classes and my protocols are getting incredibly large. And they're gonna have to start to deal with things they were never really meant to deal with. So I'm gonna start taking shortcuts, and I'm going to wind up with a big messy BurgerProduct and probably a messy burger as well. But at this point, I'm gonna run into those types of problems. So what I wanna do is, I wanna kinda start to separate those things. I wanna separate my base BurgerProduct, and then I wanna be able to add additional ingredients on top of it. But I wanna do it in such a way that the functionality that my application is providing is not going to suffer from adding cheese, and from adding lettuce, and from adding tomato. I just want it to kind of wrap around the existing product, which is a burger and that's exactly what the decorator pattern is for. So let's get rid of this contains cheese, because I think we're all in agreement that that's probably a bad idea. But how can we do that using the decorator pattern? So the first thing we're going to do, is we're going to create a decorator class. So that class is going to be a BurgerDecorator. And it's also going to implement the same protocol that we're using up here, the BurgerProduct protocol, that's very important to remember. Now what we want this BurgerDecorator to be is more of an abstract class. We really don't wanna use this directly, we're more interested in creating sub classes of this that are going to represent all of the different ingredients we can add to our burger, hence the name BurgerDecorator. So in order for this to work, our BurgerDecorator, at its base, needs to take in some sort of decorated BurgerProduct. So it needs to take in something else that implements this protocol. So we're going to store that as our decoratedBurger, and this is going to be of type BurgerProduct, like that. So then obviously we're gonna have to pass that in, so that we can save that and do something with it. So we're gonna need to create a required init function where I'm simply going to pass in a decoratedBurger, which is once again going to be of type BurgerProduct. And I'm just gonna save that, right, no big deal. We're not really doing anything here, we haven't done before. That's really not that big of a deal. All right, but since we are dealing with this as a BurgerProduct, we have to adhere to this protocol. So I need a price, ingredient and then I need those functions. So let's go ahead and add those in. And right now, because this is just our base class, I'm really not interested in doing too much functionality. I'm just going to leave these things pretty much empty for now. So we're gonna have an ingredient that's going to be empty, and we're gonna have a price that is equal to maybe $3. Maybe the base product is gonna be a $3 burger, and that's what we're gonna start with. And then of course, we have our functions, so we have our getCost. But because I'm more interested in what has been passed into this decorator. At this point, when I get the cost, I'm not interested in actually dealing with this. So I'm gonna set these down to 0 and to empty. What I'm more interested in is, I wanna get the cost associated with whatever I'm decorating, which just so happen to be this decoratedBurger. So I'm simply going to return the decoratedBurger.getCost value, okay? So nothing too crazy there. And then we're gonna do the same thing with the getIngredients. I'm more interested in what's been passed into me for each one of these things. Okay, so now we have this kind of base class, this abstract class implementation. Now we need to start actually creating concrete versions of this, and take advantage of the decorator functionality by adding the new ingredients to our burger, and having it all just work the way that we want it to. So what's the first ingredient that we want to support? So before I mentioned cheese, so we're going to create a cheese ingredient, which is going to be an implementation of our BurgerDecorator. So in order for that to hold true, I need to start to fill in some of the things that I've done down here. So now that I have my CheeseIngredient, I need to start implementing the concept of this BurgerDecorator. So the first thing that I need to do, is I need to create my init function here. So I'm gonna create my required init, and at once again, it's going to take in a decoratedBurger, which is going to be a BurgerProduct, just like that. And this time, I'm not storing that in my actual implementation, I'm going to store it in this super class up here. So in order for me to do that, I'm simply going to say superinit, and I'm gonna pass in that decoratedBurger. Okay, nothing too crazy there. But now I also wanna set the ingredient and the price here, because I actually know what is going on here, what this ingredient is as well as its price. So in order to do that, we're simply gonna say self.ingredient is going to be equal to cheese, and the self.price is gonna be equal to, I dunno maybe $1.50. Maybe that's how much it costs to add cheese to a burger. So now we also want to override the getCost function. And this getCost function is simply going to kind of delegate off and say, hey super, I want you to get me the cost of whatever the super version was, plus whatever my price is that I've set here. So now we're starting to decorate and augment the functionality by, if we're adding cheese to an existing burger. I need to get the price of the existing burger and add onto it whatever my price is. So that's where this decorator concept is coming from. So now I can also say getIngredient and we're gonna do the same thing, but we're gonna format this a little bit differently. We're gonna say super.getIngredient, and then we're just going to augment this a little bit, we'll say, we'll add to that space. And then we're going to say ingredient, like that. So now the getIngredient function is going to get whatever this super version of the ingredient is, plus whatever ingredient I'm dealing with in this, which is happening to be cheese. Okay, so now that we've got this basic concept here, how do I apply this decorator to my existing burger? Well, let's use an example to see how that would work. So let's go ahead and say var burger, so this is gonna be the base instance. And this burger is going to be of type BurgerProduct, and it's gonna be equal to a new instance of a burger. Okay, nothing too crazy there. So now let's go ahead and take a look at what the burger ingredient is. I think, which should be pretty self explanatory, which is beef. And then the burger price is going to be $3. Okay, well that's interesting, but now let's go ahead and augment this. Let's use our cheese decorator. So how do we do that? Well, we're now gonna say our burger is going to be equal to, and we wanna use our cheese ingredient, so we wanna add to it. We're gonna add to it a BurgerProduct, which is going to be our burger that we're passing in. So now we're getting a new burger that's going to have the cheese ingredient decorator on it, and how do we see what the ingredients and price, and all that stuff is now? Well, we're gonna use those functions, so now I can say getCost, and that getCost is now going to return $4.50. Well, why is that? Well, we started with a base burger, that base burger was $3 and its ingredient was beef. So now, I added the CheeseIngredient decorator onto it. And as you can see my getCost and getIngredients are now gonna say for the cost, get the cost of the BurgerProduct that I was added on top of which was $3. And now add to at my price which is $1.50, which gives us a total of $4.50. Okay, and now I can say burger.getIngredient, and so now at this point instead of being just beef, I'm also going to get beef and cheese. So now we can see how, we can begin to augment functionality of a particular base class that we like, without having to introduce a lot of complexity and extra functions, and extra properties inside that base class. We can now begin to create decorators that we can apply on top of that class to add and augment the functionality. And now that we've created one ingredient, we can create several of these. We could add lettuce and tomato, and all those types of things. And just build on the existing functionality and continue to create new products with very little effort.







