Hi everyone, so I went through most of the common Structural Patterns and the following will be an article about a recent model that is no longer popular because people are getting richer and upgrading RAM is very simple. @ Flyweight Pattern!
Intent (general)
** Flyweight ** is a structure design that allows you to save more objects into the amount of RAM available by sharing common state parts between multiple objects instead of keeping all the data in each object.
Problem
To have a fun after long hours of work, you decide to create a simple video game: players will move around the map and shoot each other. You have chosen to implement an actual particle system and make it a special feature of the game. A large number of bullets, rockets and shrapnel from the explosion will fly around the map and bring thrilling experience for players.
Once completed, push the commit, build the game, and send it to your friend to try it out. Although the game has run perfectly on your computer, your friend could not play for long. On my computer, the game keeps crashing after a few minutes of playing the game. After spending a few hours digging through the debug logs, you discover that the game has crashed due to insufficient RAM.
Practical problems related to your particle system. Each particle, such as a bullet, a rocket or a piece of ammunition is represented by a separate object that contains a lot of data. At some point, when the slaughter on the player screen reached its climax, the newly created particles no longer matched the remaining RAM, so the program crashed.
Solution
On closer inspection of the Particle
class, you may notice that the color and sprite fields consume a lot more memory than other fields. What’s worse is that these two fields store almost identical data across all particles. For example, all bullets have the same color and sprite.
Upon closer inspection of the Particle
class, you may notice that the color and sprite fields use more memory than the other fields. What’s worse is that these two fields store almost identical data on all particles. For example, all bullets have the same color and sprite.
The states of other particles, such as coordinates, movement vector and speed, are unique to each particle. The values of these fields change the context. while color and sprite unchanged.
The constant data of an object is often called the intrinsic state . Other objects can only read it, cannot change it. The rest of the object state, often altered by other objects, is called extrinsic state .
The Flyweight pattern suggests that you stop storing extrinsic state inside the object. Instead, you should convert these states to specific methods based on it. Only the intrinsic state is in the object, allowing you to reuse it in different contexts.
Back to our game. Assuming that we extracted the outer state from our particle layer, there are only three different objects enough to represent all the particles in the game: a bullet, a rocket and a shard. As you can guess now, an object that only stores internal state is called flyweight.
Extrinsic state storage
Where does the outside move to? Some classes should still store it, right? In most cases, it is moved to the container object, it aggregates the objects before we apply the template.
In our case, it’s the main Game
object that stores all the particles in the particles
field. To move an external state into this class, you need to create several array fields to store the coordinates, vectors, and speed of each particle individually. But that is not all. You need another array to store references to a specific flyweight that represents a county. These arrays must be synchronized so that you can access all the data for a particle using the same index.
A better solution is to create a separate context class that will store the external state along with a reference to the flyweight object. This approach will require only a single array in the container class.
Oh wait !! Do we need to have as many contextual objects as we initially have? Technically, yes. But the important thing is, the objects are much smaller than before. The most memory-intensive fields have been moved to only a few flyweight objects. Now, a thousand small contextual objects can reuse the heaviest flyweight object instead of storing a thousand copies of its data.
Flyweight and immutability
Because the same flyweight object can be used in different contexts, you must make sure that its state cannot be modified. A flyweight should initialize its state only once, via the constructor parameters.
Flyweight factory
For more convenient access to the different flyweight, you can create a factory method that manages an existing group of flyweight objects. The method of getting the intrinsic state of the desired flyweight from the client as a parameter, searching for an existing flyweight object that matches this state and returning it if found. If not, it creates a new flyweight object and adds it to memory reuse.
Structure (organization)
- The Flyweight pattern is merely an optimization. Before applying it, make sure your program has RAM consumption issues related to having a large number of similar objects in memory at the same time. Make sure this problem cannot be resolved in any other way.
- The Flyweight class contains an initial state of an object that can be shared among multiple objects. The same flyweight object can be used in many different contexts. The state stored inside a flyweight is called an “intrinsic”. The state passed to the flyweight method is called “extrinsic”.
- The Context class contains an extrinsic state. When a context is paired with one of the flyweight objects, it represents the full state of the original object.
- Typically, the behavior of the original object is still in the Flyweight class; in this case, whenever calling a flyweight method, it must pass the appropriate extrinsic state values to the method’s parameters. On the other hand, the behavior can be passed to the Context class, which will use the associated flyweight as a data object only.
- The client calculates or stores the extrinsic state of the flyweights. From a client’s perspective, flyweight is a sample object that can be configured at run time by passing some contextual data into the parameters of its methods.
- Flyweight Factory manages a group of existing flyweight. The client will not create flyweights directly. Instead, they call the factory, passing it the intrinsic state of the desired flyweight. The factory looks at the previously created flyweight and returns an existing one that matches the search criteria or creates a new one if nothing is found.
Applicability (use when)
Use the Flyweight pattern only when your program must support a large number of objects that barely fit into the available RAM.
How to Implement (how to install)
- Dividing the fields of a class into a flyweight into two parts:
- intrinsic state: fields that contain immutable and duplicate data on multiple objects
- extrinsic state: fields that contain unique contextual data for each object
- Leaving the fields representing the intrinsic state in the class, make sure they are unchanged and only have their original values from the constructor.
- Eliminate extrinsic state fields. Replace them with methods that represent the values of extrinsic state fields.
- This is optional, create a factory class to manage the flyweights group. It should check an existing factory before creating a new one. Once the factory is up and running, customers only have to request the flyweight through it. They should describe the desired flyweight by moving its intrinsic state to the factory.
- The client must store or calculate values of extrinsic state (context) in order to call methods of flyweight objects. For convenience, the extrinsic state along with the field referencing the flyweight can be converted to a separate context class.
Decorator in Ruby (example with ruby language)
main.rb: Conceptual Example
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 | <span class="token keyword">require</span> <span class="token string">'json'</span> <span class="token keyword">class</span> <span class="token class-name">Flyweight</span> <span class="token comment"># @param [String] shared_state</span> <span class="token keyword">def</span> <span class="token function">initialize</span> <span class="token punctuation">(</span> shared_state <span class="token punctuation">)</span> <span class="token variable">@shared_state</span> <span class="token operator">=</span> shared_state <span class="token keyword">end</span> <span class="token comment"># @param [String] unique_state</span> <span class="token keyword">def</span> <span class="token function">operation</span> <span class="token punctuation">(</span> unique_state <span class="token punctuation">)</span> s <span class="token operator">=</span> <span class="token variable">@shared_state</span> <span class="token punctuation">.</span> to_json u <span class="token operator">=</span> unique_state <span class="token punctuation">.</span> to_json print <span class="token string">"Flyweight: Displaying shared ( <span class="token interpolation"><span class="token delimiter tag">#{</span> s <span class="token delimiter tag">}</span></span> ) and unique ( <span class="token interpolation"><span class="token delimiter tag">#{</span> u <span class="token delimiter tag">}</span></span> ) state."</span> <span class="token keyword">end</span> <span class="token keyword">end</span> <span class="token comment"># The Flyweight Factory creates and manages the Flyweight objects. It ensures</span> <span class="token comment"># that flyweights are shared correctly. When the client requests a flyweight,</span> <span class="token comment"># the factory either returns an existing instance or creates a new one, if it</span> <span class="token comment"># doesn't exist yet.</span> <span class="token keyword">class</span> <span class="token class-name">FlyweightFactory</span> <span class="token comment"># @param [Hash] initial_flyweights</span> <span class="token keyword">def</span> <span class="token function">initialize</span> <span class="token punctuation">(</span> initial_flyweights <span class="token punctuation">)</span> <span class="token variable">@flyweights</span> <span class="token operator">=</span> <span class="token punctuation">{</span> <span class="token punctuation">}</span> initial_flyweights <span class="token punctuation">.</span> <span class="token keyword">each</span> <span class="token keyword">do</span> <span class="token operator">|</span> state <span class="token operator">|</span> <span class="token variable">@flyweights</span> <span class="token punctuation">[</span> <span class="token function">get_key</span> <span class="token punctuation">(</span> state <span class="token punctuation">)</span> <span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token constant">Flyweight</span> <span class="token punctuation">.</span> <span class="token keyword">new</span> <span class="token punctuation">(</span> state <span class="token punctuation">)</span> <span class="token keyword">end</span> <span class="token keyword">end</span> <span class="token comment"># Returns a Flyweight's string hash for a given state.</span> <span class="token keyword">def</span> <span class="token function">get_key</span> <span class="token punctuation">(</span> state <span class="token punctuation">)</span> state <span class="token punctuation">.</span> sort <span class="token punctuation">.</span> <span class="token function">join</span> <span class="token punctuation">(</span> <span class="token string">'_'</span> <span class="token punctuation">)</span> <span class="token keyword">end</span> <span class="token comment"># Returns an existing Flyweight with a given state or creates a new one.</span> <span class="token keyword">def</span> <span class="token function">get_flyweight</span> <span class="token punctuation">(</span> shared_state <span class="token punctuation">)</span> key <span class="token operator">=</span> <span class="token function">get_key</span> <span class="token punctuation">(</span> shared_state <span class="token punctuation">)</span> <span class="token keyword">if</span> <span class="token operator">!</span> <span class="token variable">@flyweights</span> <span class="token punctuation">.</span> key <span class="token operator">?</span> <span class="token punctuation">(</span> key <span class="token punctuation">)</span> puts <span class="token string">"FlyweightFactory: Can't find a flyweight, creating new one."</span> <span class="token variable">@flyweights</span> <span class="token punctuation">[</span> key <span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token constant">Flyweight</span> <span class="token punctuation">.</span> <span class="token keyword">new</span> <span class="token punctuation">(</span> shared_state <span class="token punctuation">)</span> <span class="token keyword">else</span> puts <span class="token string">'FlyweightFactory: Reusing existing flyweight.'</span> <span class="token keyword">end</span> <span class="token variable">@flyweights</span> <span class="token punctuation">[</span> key <span class="token punctuation">]</span> <span class="token keyword">end</span> <span class="token keyword">def</span> list_flyweights puts <span class="token string">"FlyweightFactory: I have <span class="token interpolation"><span class="token delimiter tag">#{</span> @flyweights <span class="token punctuation">.</span> size <span class="token delimiter tag">}</span></span> flyweights:"</span> print <span class="token variable">@flyweights</span> <span class="token punctuation">.</span> keys <span class="token punctuation">.</span> <span class="token function">join</span> <span class="token punctuation">(</span> <span class="token string">"n"</span> <span class="token punctuation">)</span> <span class="token keyword">end</span> <span class="token keyword">end</span> <span class="token comment"># @param [FlyweightFactory] factory</span> <span class="token comment"># @param [String] plates</span> <span class="token comment"># @param [String] owner</span> <span class="token comment"># @param [String] brand</span> <span class="token comment"># @param [String] model</span> <span class="token comment"># @param [String] color</span> <span class="token keyword">def</span> <span class="token function">add_car_to_police_database</span> <span class="token punctuation">(</span> factory <span class="token punctuation">,</span> plates <span class="token punctuation">,</span> owner <span class="token punctuation">,</span> brand <span class="token punctuation">,</span> model <span class="token punctuation">,</span> color <span class="token punctuation">)</span> puts <span class="token string">"nnClient: Adding a car to database."</span> flyweight <span class="token operator">=</span> factory <span class="token punctuation">.</span> <span class="token function">get_flyweight</span> <span class="token punctuation">(</span> <span class="token punctuation">[</span> brand <span class="token punctuation">,</span> model <span class="token punctuation">,</span> color <span class="token punctuation">]</span> <span class="token punctuation">)</span> <span class="token comment"># The client code either stores or calculates extrinsic state and passes it to</span> <span class="token comment"># the flyweight's methods.</span> flyweight <span class="token punctuation">.</span> <span class="token function">operation</span> <span class="token punctuation">(</span> <span class="token punctuation">[</span> plates <span class="token punctuation">,</span> owner <span class="token punctuation">]</span> <span class="token punctuation">)</span> <span class="token keyword">end</span> <span class="token comment"># The client code usually creates a bunch of pre-populated flyweights in the</span> <span class="token comment"># initialization stage of the application.</span> factory <span class="token operator">=</span> <span class="token constant">FlyweightFactory</span> <span class="token punctuation">.</span> <span class="token keyword">new</span> <span class="token punctuation">(</span> <span class="token punctuation">[</span> <span class="token string">%w[Chevrolet Camaro2018 pink]</span> <span class="token punctuation">,</span> <span class="token punctuation">[</span> <span class="token string">'Mercedes Benz'</span> <span class="token punctuation">,</span> <span class="token string">'C300'</span> <span class="token punctuation">,</span> <span class="token string">'black'</span> <span class="token punctuation">]</span> <span class="token punctuation">,</span> <span class="token punctuation">[</span> <span class="token string">'Mercedes Benz'</span> <span class="token punctuation">,</span> <span class="token string">'C500'</span> <span class="token punctuation">,</span> <span class="token string">'red'</span> <span class="token punctuation">]</span> <span class="token punctuation">,</span> <span class="token string">%w[BMW M5 red]</span> <span class="token punctuation">,</span> <span class="token string">%w[BMW X6 white]</span> <span class="token punctuation">]</span> <span class="token punctuation">)</span> factory <span class="token punctuation">.</span> list_flyweights <span class="token function">add_car_to_police_database</span> <span class="token punctuation">(</span> factory <span class="token punctuation">,</span> <span class="token string">'CL234IR'</span> <span class="token punctuation">,</span> <span class="token string">'James Doe'</span> <span class="token punctuation">,</span> <span class="token string">'BMW'</span> <span class="token punctuation">,</span> <span class="token string">'M5'</span> <span class="token punctuation">,</span> <span class="token string">'red'</span> <span class="token punctuation">)</span> <span class="token function">add_car_to_police_database</span> <span class="token punctuation">(</span> factory <span class="token punctuation">,</span> <span class="token string">'CL234IR'</span> <span class="token punctuation">,</span> <span class="token string">'James Doe'</span> <span class="token punctuation">,</span> <span class="token string">'BMW'</span> <span class="token punctuation">,</span> <span class="token string">'X1'</span> <span class="token punctuation">,</span> <span class="token string">'red'</span> <span class="token punctuation">)</span> puts <span class="token string">"nn"</span> factory <span class="token punctuation">.</span> list_flyweights |
output.txt: Execution result
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | FlyweightFactory: I have 5 flyweights: Camaro2018_Chevrolet_pink C300_Mercedes Benz_black C500_Mercedes Benz_red BMW_M5_red BMW_X6_white Client: Adding a car to database. FlyweightFactory: Reusing existing flyweight. Flyweight: Displaying shared (["BMW","M5","red"]) and unique (["CL234IR","James Doe"]) state. Client: Adding a car to database. FlyweightFactory: Can't find a flyweight, creating new one. Flyweight: Displaying shared (["BMW","X1","red"]) and unique (["CL234IR","James Doe"]) state. FlyweightFactory: I have 6 flyweights: Camaro2018_Chevrolet_pink C300_Mercedes Benz_black C500_Mercedes Benz_red BMW_M5_red BMW_X6_white BMW_X1_red |