- Overview
- Transcript
3.2 Flyweight
The goal of the flyweight pattern is to minimize resource consumption by reusing as much functionality as possible. The basic concept in the flyweight implementation is caching. Every time we need to create a specific instance of a type, we store it for later reuse.
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.2 Flyweight
Let's talk about another structural design pattern that has a very interesting name. And I didn't realize that I was using it when I was using it quite some years ago. And you may have run into this before as well, where you've used the pattern before but you maybe didn't realized it was a pattern or it had a name. And this is definitely one that could fall into that category, and this is known as the Flyweight pattern. So, let's go ahead and create another playground here for the Flyweight pattern. So, what exactly is the Flyweight pattern? Well, it's a way to kind of optimize some of your code for performance, and try to take advantage of the fact that there's already some implementations within your code of some functionality without having to recreate it over and over again. And that, if you've already created an instance of it, to be able to hold on to that, and then reuse it in other places in your code when necessary. So, you don't have to continually create new instances of this class and things of that nature. So, let's kind of go through a simple example here. Let's say that I have a protocol. And this protocol is going to be something rather generic. We'll just call this the Executor, something along those lines, and this particular protocol simply defines a function called execute. Big surprise? So, we have some sort of piece of functionality, or in a protocol, that we want to be able to share across some implementations, so that they all have this particular function. So, what we're gonna do, let's just say we have some services. Let's create a class called Service, and this Service is going to implement this Executor. And the way that we're differentiating these services is by what we are passing in to these services, we can pass in a type maybe, so that defines what sort of type this services or something like that. So, we could do something like public let and we'll say type is going to a String. And we will create an init here that's going to take that in, so we can say type is going to be String. And we'll simply say, self.type is gonna be equal to type, like that. So, this is a basic service that we're defining, but then we also have to be sure that we implement our Executor protocol. And we're simply going to come in here and just, we'll just print something out really simple. We'll just say Congratulations, you just did something great. Whatever that happens to be. So, imagine there's some sort of fancy implementation in here. And that is going to get executed every time we run this particular function. And we could have a number of these different services. But the only thing about this service that knows that it's different from other ones is the fact that it's some sort of type. So, these are generic services, maybe it's some sort of base class that you are implementing, or creating different versions of, and you are telling it what type it is, and based on that type it's doing some sort of functionality. Something along those lines. Well, what if within these services, things could get fairly complex. You could be using a lot of memory. You could have to do a lot of work. It could have to hold on to a lot of information. It could be very intensive, a lot of work going on here. And the problem is, if I start to create multiple instances of this particular class, we're gonna start eating up a lot of resources. Because we now we have to make copies of all of this, and I can have ten different instances of this service running around, and they can all be holding on to memory and I/O, and all those types of things, which is not very performing and will obviously cause a performance degradation within your system. So, what I want to do is I want to use the Flyweight pattern to be a little bit smarter about how I create instances of these services. Maybe I want to say, all right, I want to create an instance of this service and I wanna use it right now. But, the next time I need an instance of that type of service, I just wanna reuse the one I've already created. There's no sense in me creating, multiple instances of this and that's the basic definition of the Flyweight patterns. So, how do we Implement something like that? Well, traditionally you would do it via a factory. So, we're gonna kinda start to blend together the concept of creational and structural patterns here for Flyweight. So, let's create a simple class called the ServiceFactory here. And, within the ServiceFactory, we're gonna want to have some sort of function in here that's going to retrieve or create some sort of instance of this service. So, let's just say we'll call this getService. This is gonna be our function, so we'll say, function, getService. Now, like I said before, what we're doing here is we are giving these services a type, which is kind of an identifier to say, every time I need maybe an order service. I want to get the same instance of that order service. And, every time I need a billing service, I want to get the same instance of that billing service. I don't want a bunch of different instances of those running around. So, one thing I could do here is I could say, inside of this ServiceFactory, I want to get a service that is able to be referenced by a key, which is going to be a String, and it's going to return back to me an instance of a service. Okay, well we're getting somewhere but how do I handle this? Well, typically, the way that you're going to do this, is you're going to have some sort of mechanism that's going to allow you to hold on to these instances of these classes, so that you can use them and check to see if they exist. And if they do, you could send that instance back, and if they don't, you can kinda create and register it for use now and anytime later that you need it. So, the way that we're gonna do that is simply by creating a little bit of a private dictionary here, and we'll call this services. And we're gonna say that this is going to be a dictionary where the key is a String and the value is going to be a Service, and we're just going to initialize this to be an empty dictionary. So, when we first start this up, our service factory is not gonna contain any registered services, because we haven't actually needed anything yet. So, now what we're gonna need to do is we're going to come down into our getService function, And we want to look and see within that dictionary if the service that I need already exist. So, we're gonna do that with a simple if let, we're gonna say if let service is equal to services, and I want to retrieve a service that has that particular key that is of that type. So, I can say, I want to get an order service. Do I have an order service in there? If I do, then I simply want to return that service, just like that. But if I don't, then I need to have an else block here, and I need to create a new instance of that. So, I can say let service be equal to a new instance of a service. And I'm creating this of a certain type of that key that I'm passing in. And then before I return that, I wanna register that within my factory. So, now I can say services, for that particular key, I can set it equal to that instance of the service, and then I'm going to return that service out of my factory. So, that looks like it's going to work now. So, what's the big deal? Well, like I said before, the goal here is to reduce the amount of resources I'm using locally on whatever platform I'm running this on, especially if I'm running this on say, a mobile device where things, I need to be a little bit more cognizant of the amount of resources that I'm using. So, how can we verify that this truly works? Well, the way that you do that is by requesting the same type of an object multiple times, and make sure that you're getting the same object or objects that reference the same memory every single time. So, let's see how we would do that. So, let's create a new factory, and this is gonna be equal to our ServiceFactory, so let's create a new instance of that. And then we'll say all right, I want to get a service, so I'm gonna use my factory to get a service, and this service, let's just say this is an order service. So, that's the one that I want to retrieve, so I'm gonna get a service back. So, let's now do another one, let's say I want to get service to, and then we're gonna go in here and we're gonna say factory.getService, and I want to get another order service. And then we will get a third one, so that we can just show that these are all going to be different, we can say factory.getService, and this time let's call this billing. So, I wanna get two order services and a billing service. Now, how do I know that these things are the same? Well, within Swift, I can use the triple equals sign to verify that these things are truly referencing the same object. So, the way that we do that, is we can say that service === service2. And if it's true, that means both of these variables are pointing to the same memory space. And as you can see here, the result is true. Now, if they're not the same object, and they're not referencing the same data, then you would get a false here. So, now I can say also, service === service3. Now, I'd expect this to be false. And it is, because in this case, I'm getting a new service with a key of billing, so now within my ServiceFactory, I have two different types of services that have been registered. I have an order service and I have a billing service, and they're being stored within here. So, every time I go to retrieve a service, I'm gonna first look within my registered collection, and if one already exists, I'm gonna get the existing implementation, and I can go ahead and use it. But if it doesn't exist, I'm going to create a new one. I'm going to register that, and then I'll return it so that any time later on, in this case when I need a billing service, or an order service, I can retrieve an instance that's already been created, and I can save myself the trouble and the performance hit of having to recreate these services over and over again, every single time.







