Flyweight Pattern (#12)

* edit documentation

* add code example

Co-authored-by: Ruidy <r.nemausat@empfohlen.de>
This commit is contained in:
Ruidy 2020-09-27 21:25:55 +02:00 committed by GitHub
parent 834f1ded23
commit 664b39c32e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 140 additions and 0 deletions

View file

@ -16,3 +16,4 @@
- [Composite](structural/composite/README.md)
- [Decorator](structural/decorator/README.md)
- [Facade](structural/facade/README.md)
- [Flyweight](structural/flyweight/README.md)

View file

@ -7,3 +7,4 @@ Structural patterns explain how to assemble objects and classes into larger stru
- [Composite](composite/README.md)
- [Decorator](decorator/README.md)
- [Facade](facade/README.md)
- [Flyweight](flyweight/README.md)

View 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 friends rig was much less powerful than your own computer, and thats 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 players 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. Whats 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 particles 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 objects 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, youd 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 theyre 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.

View file

View 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="")

View 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="")

View 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()