mirror of
https://github.com/rjNemo/design-patterns
synced 2026-06-06 02:26:40 +00:00
Flyweight Pattern (#12)
* edit documentation * add code example Co-authored-by: Ruidy <r.nemausat@empfohlen.de>
This commit is contained in:
parent
834f1ded23
commit
664b39c32e
7 changed files with 140 additions and 0 deletions
|
|
@ -16,3 +16,4 @@
|
||||||
- [Composite](structural/composite/README.md)
|
- [Composite](structural/composite/README.md)
|
||||||
- [Decorator](structural/decorator/README.md)
|
- [Decorator](structural/decorator/README.md)
|
||||||
- [Facade](structural/facade/README.md)
|
- [Facade](structural/facade/README.md)
|
||||||
|
- [Flyweight](structural/flyweight/README.md)
|
||||||
|
|
|
||||||
|
|
@ -7,3 +7,4 @@ Structural patterns explain how to assemble objects and classes into larger stru
|
||||||
- [Composite](composite/README.md)
|
- [Composite](composite/README.md)
|
||||||
- [Decorator](decorator/README.md)
|
- [Decorator](decorator/README.md)
|
||||||
- [Facade](facade/README.md)
|
- [Facade](facade/README.md)
|
||||||
|
- [Flyweight](flyweight/README.md)
|
||||||
|
|
|
||||||
37
structural/flyweight/README.md
Normal file
37
structural/flyweight/README.md
Normal file
|
|
@ -0,0 +1,37 @@
|
||||||
|
# Flyweight
|
||||||
|
|
||||||
|
Flyweight is a structural design pattern that lets you fit more objects into the available amount of RAM by sharing common parts of state between multiple objects instead of keeping all of the data in each object.
|
||||||
|
|
||||||
|
## Problem
|
||||||
|
|
||||||
|
You decided to create a simple video game: players would be moving around a map and shooting each other. You chose to
|
||||||
|
implement a realistic particle system. Vast quantities of bullets, missiles, and shrapnel from explosions should fly all over the map and deliver a thrilling experience to the player.
|
||||||
|
|
||||||
|
Upon its completion, you built the game and sent it to your friend for a test drive. Although the game was running flawlessly on your machine, your friend wasn't able to play for long. On his computer, the game kept crashing after a few minutes of gameplay. After spending several hours digging through debug logs, you discovered that the game crashed because of an insufficient amount of RAM. It turned out that your friend’s rig was much less powerful than your own computer, and that’s why the problem emerged so quickly on his machine.
|
||||||
|
|
||||||
|
The actual problem was related to your particle system. Each particle, such as a bullet, a missile or a piece of shrapnel was represented by a separate object containing plenty of data. At some point, when the carnage on a player’s screen reached its climax, newly created particles no longer fit into 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.
|
||||||
|
|
||||||
|
Other parts of a particle’s state, such as coordinates, movement vector and speed, are unique to each particle. After all, the values of these fields change over time. This data represents the always changing context in which the particle exists, while the color and sprite remain constant for each particle.
|
||||||
|
|
||||||
|
This constant data of an object is usually called the intrinsic state. It lives within the object; other objects can only read it, not change it. The rest of the object’s state, often altered “from the outside” by other objects, is called the extrinsic state.
|
||||||
|
|
||||||
|
The Flyweight pattern suggests that you stop storing the extrinsic state inside the object. Instead, you should pass this state to specific methods which rely on it. Only the intrinsic state stays within the object, letting you reuse it in different contexts. As a result, you’d need fewer of these objects since they only differ in the intrinsic state, which has much fewer variations than the extrinsic.
|
||||||
|
|
||||||
|
## How to Implement
|
||||||
|
|
||||||
|
1. Divide fields of a class that will become a flyweight into two parts:
|
||||||
|
|
||||||
|
- the intrinsic state: the fields that contain unchanging data duplicated across many objects
|
||||||
|
- the extrinsic state: the fields that contain contextual data unique to each object
|
||||||
|
1. Leave the fields that represent the intrinsic state in the class, but make sure they’re immutable. They should take their initial values only inside the constructor.
|
||||||
|
|
||||||
|
1. Go over methods that use fields of the extrinsic state. For each field used in the method, introduce a new parameter and use it instead of the field.
|
||||||
|
|
||||||
|
1. Optionally, create a factory class to manage the pool of flyweights. It should check for an existing flyweight before creating a new one. Once the factory is in place, clients must only request flyweights through it. They should describe the desired flyweight by passing its intrinsic state to the factory.
|
||||||
|
|
||||||
|
1. The client must store or calculate values of the extrinsic state (context) to be able to call methods of flyweight objects. For the sake of convenience, the extrinsic state along with the flyweight-referencing field may be moved to a separate context class.
|
||||||
|
|
||||||
0
structural/flyweight/__init__.py
Normal file
0
structural/flyweight/__init__.py
Normal file
20
structural/flyweight/flyweight.py
Normal file
20
structural/flyweight/flyweight.py
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
import json
|
||||||
|
from typing import List
|
||||||
|
|
||||||
|
|
||||||
|
class Flyweight:
|
||||||
|
"""
|
||||||
|
The Flyweight stores a common portion of the state (also called intrinsic
|
||||||
|
state) that belongs to multiple real business entities. The Flyweight
|
||||||
|
accepts the rest of the state (extrinsic state, unique for each entity) via
|
||||||
|
its method parameters.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, shared_state: List[str]) -> None:
|
||||||
|
self._shared_state = shared_state
|
||||||
|
|
||||||
|
def operation(self, unique_state: List[str]) -> None:
|
||||||
|
s = json.dumps(self._shared_state)
|
||||||
|
u = json.dumps(unique_state)
|
||||||
|
print(f"Flyweight: Displaying shared ({s}) and unique ({u}) state.",
|
||||||
|
end="")
|
||||||
44
structural/flyweight/flyweight_factory.py
Normal file
44
structural/flyweight/flyweight_factory.py
Normal file
|
|
@ -0,0 +1,44 @@
|
||||||
|
from typing import Dict, List
|
||||||
|
|
||||||
|
from structural.flyweight.flyweight import Flyweight
|
||||||
|
|
||||||
|
|
||||||
|
class FlyweightFactory:
|
||||||
|
"""
|
||||||
|
The Flyweight Factory creates and manages the Flyweight objects. It ensures
|
||||||
|
that flyweights are shared correctly. When the client requests a flyweight,
|
||||||
|
the factory either returns an existing instance or creates a new one, if it
|
||||||
|
doesn't exist yet.
|
||||||
|
"""
|
||||||
|
|
||||||
|
_flyweights: Dict[str, Flyweight] = {}
|
||||||
|
|
||||||
|
def __init__(self, initial_flyweights: List) -> None:
|
||||||
|
for state in initial_flyweights:
|
||||||
|
self._flyweights[self.get_key(state)] = Flyweight(state)
|
||||||
|
|
||||||
|
def get_key(self, state: List) -> str:
|
||||||
|
"""
|
||||||
|
Returns a Flyweight's string hash for a given state.
|
||||||
|
"""
|
||||||
|
|
||||||
|
return "_".join(sorted(state))
|
||||||
|
|
||||||
|
def get_flyweight(self, shared_state: List) -> Flyweight:
|
||||||
|
"""
|
||||||
|
Returns an existing Flyweight with a given state or creates a new one.
|
||||||
|
"""
|
||||||
|
key = self.get_key(shared_state)
|
||||||
|
|
||||||
|
if not self._flyweights.get(key):
|
||||||
|
print("FlyweightFactory: Can't find a flyweight, creating new one.")
|
||||||
|
self._flyweights[key] = Flyweight(shared_state)
|
||||||
|
else:
|
||||||
|
print("FlyweightFactory: Reusing existing flyweight.")
|
||||||
|
|
||||||
|
return self._flyweights[key]
|
||||||
|
|
||||||
|
def list_flyweights(self) -> None:
|
||||||
|
count = len(self._flyweights)
|
||||||
|
print(f"FlyweightFactory: I have {count} flyweights:")
|
||||||
|
print("\n".join(map(str, self._flyweights.keys())), end="")
|
||||||
37
structural/flyweight/main.py
Normal file
37
structural/flyweight/main.py
Normal file
|
|
@ -0,0 +1,37 @@
|
||||||
|
from structural.flyweight.flyweight_factory import FlyweightFactory
|
||||||
|
|
||||||
|
|
||||||
|
def add_car_to_police_database(factory: FlyweightFactory, plates: str,
|
||||||
|
owner: str, brand: str, model: str,
|
||||||
|
color: str) -> None:
|
||||||
|
|
||||||
|
print("\n\nClient: Adding a car to database.")
|
||||||
|
flyweight = factory.get_flyweight([brand, model, color])
|
||||||
|
# The client code either stores or calculates extrinsic state and passes it
|
||||||
|
# to the flyweight's methods.
|
||||||
|
flyweight.operation([plates, owner])
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
"""
|
||||||
|
The client code usually creates a bunch of pre-populated flyweights in the
|
||||||
|
initialization stage of the application.
|
||||||
|
"""
|
||||||
|
factory = FlyweightFactory([
|
||||||
|
["Chevrolet", "Camaro2018", "pink"],
|
||||||
|
["Mercedes Benz", "C300", "black"],
|
||||||
|
["Mercedes Benz", "C500", "red"],
|
||||||
|
["BMW", "M5", "red"],
|
||||||
|
["BMW", "X6", "white"],
|
||||||
|
])
|
||||||
|
|
||||||
|
factory.list_flyweights()
|
||||||
|
|
||||||
|
add_car_to_police_database(
|
||||||
|
factory, "CL234IR", "James Doe", "BMW", "M5", "red")
|
||||||
|
add_car_to_police_database(
|
||||||
|
factory, "CL234IR", "James Doe", "BMW", "X1", "red")
|
||||||
|
|
||||||
|
print("\n")
|
||||||
|
|
||||||
|
factory.list_flyweights()
|
||||||
Loading…
Reference in a new issue