The Flyweight pattern is a structural pattern, which uses “sharing to support large numbers of fine-grained objects efficiently.” In simpler terms, it allows sharing common parts of each
Trang 1
VIETNAM NATIONAL UNIVERSITY HO CHI MINH CITY
UNIVERSITY OF SCIENCE
C KHOA
DHQG-HCM
oO
SEMINAR REPORT
FLYWEIGHT DESIGN PATTERN
Members of Group 05: Nguyễn Hữu Quốc Thắng
Nguyễn Chính Thông Pham Trung Nghĩa Nguyễn Đức Thọ
Ho Chi Minh City, 16/12/2023
Trang 2
Contents
Definition
Example
Real World Problems
Pros/Cons of State
11
12
Trang 31
1
8
9
10
Imagine you’re building a game, where the purpose is to answer the age old question: “Why
does the chicken cross the road?”
Yes, we’re building a crossing game Like Crossy Road [1] Or Frogger [2]
When we start a new game, a new World should be created Where there are trees, rocks, and, of course, obstacles, like water blocks, trains, and the like
Let’s start with creating a game world The world should have many lanes, like normal land, train tracks, river blocks, and roads We call these “blocks” These blocks have some properties, such as its sprite, its position, its type, what kind of obstacle it contains, and so on
On code, a block would look like this [3]:
class Block
{
protected:
sf:
sf:
sf:
sf:
:Sprite m_sprite;
:Texture m_texture;
:Vector2f m_position;
:Vector2f m_scale = {1, 1};
ObstacleType m_obstacleType;
Obstacle *obstacle;
bool m_isContainObs = 0;
BlockType m_type;
bool m đirecion; //1-left, 0-right
And a single lane would look like this [4]:
class Lane
{
private:
sf::Vector2f m_startingPosition;
vector<Block *> blocks;
float v = 0;
public:
Lane();
~Lane();
Trang 4void addBlock(Block *block);
void draw(sf::RenderWindow &window);
Block* getBlock(int index) ;
void Update(const sf::Time& 1 time);
bool isAbleToStepOn(int index) ;
int getV();
NONE =0
DUCKWEED
RIVERLOG
TREE
CAR
After compiling the code and running it, suddenly, the computer’s fans begin to speed up
to maximum When you open Task Manager, you noticed the GPU usage spiked up to 100%,
BlockType
FINISH = -1
LAND
WATER
ROAD
Block
#m_spmite: sh:Sprite
# m_texture: sf: Texture
# m_position: sf::Vector2f
# m_scale: sf: Vector2t
# m_obstacleType: Obstacle Type
# obstacle: Obstacle
# m_type: BlockType
#m_isContainObs = 0: bool
} ——<> - blocks: std::vector<Block *>
- velocity = 0: float
+ addBlock(block: Block"): void
World
+ generateLane(); void + generateLane(type: BlockType): void
Figure 1: Diagram of the naive solution
and you have no idea why
void World::draw(sf-:RenderWindow &window) {
for (int | = lanes.size() - 1; 1 >= 0; i-){ lanes{i}->draw(window),
m_player->draw(window):
}
Trang 5
File Options View Processes Performance App history Startup Users Details Services
38% 0% 0% Y 99%
Memory Disk} Network GPU engine
ñ#T Client Ser/er Runtime Process 0.2% 14MB OMB/s O Mbps 0.1% GPUO-:
3 Service Host: Diagnostic Service 0.9 MB OMB/s O Mbps
=5 Service Host: Group Policy Client 1.2MB OMB/s O Mbps Adobe IPC Broker (32 bit) 06MB OMB/s O Mbps i] Antimalware Core Service 14 MB O MB/s 0 Mbps
Figure 2: GPU maxed out on running the C$202 Project
The problem is the design of the lanes itself Because the data of each block is stored on a single object, it begins to fill wp the available memory This becomes especially alarming when
we develop a level progression system, where the number of levels begin to increase, and so
is the lane count for each level And that’s not accounting for each of the obstacles that are present on each lane, such as the cars on the road, the small logs on the rivers, the trains,
On many limited-spec machines, it would crash immediately, while on stronger machines,
it would hamper the system’s performance massively
The Flyweight pattern is a structural pattern, which uses “sharing to support large numbers
of fine-grained objects efficiently.”
In simpler terms, it allows sharing common parts of each objects instead of keeping all the data in one single object, greatly reducing RAM usage
The Flyweight pattern is merely an optimization Before applying it, make sure your program does have the RAM consumption problem related to having a massive number of similar objects in memory at the same time Make sure that this problem can’t be solved in any other meaningful way
Let have a look at the structure of Flyweight pattern:
Trang 6
FlyweightFactory Client
- cache: Flyweight[] ©
- flyweight
+ operation()
factory.getFlyweight(repeatingState)
- repeatingState
Figure 3: Structure of FlyWeight Pattern
The Flyweight class contains the portion of the original object’s state that can be shared between multiple objects The same flyweight object can be used in many different contexts The state stored inside a flyweight is called intrinsic The state passed to the flyweight’s
methods is called extrinsic
The Context class contains the extrinsic state, unique across all original objects When a
context is paired with one of the flyweight objects, it represents the full state of the original
object
Usually, the behavior of the original object remains in the flyweight class In this case, whoever calls a flyweight’s method must also pass appropriate bits of the extrinsic state into the method’s parameters On the other hand, the behavior can be moved to the context class, which would use the linked flyweight merely as a data object
The Client calculates or stores the extrinsic state of flyweights From the client’s per- spective, a flyweight is a template object which can be configured at runtime by passing some contextual data into parameters of its methods
The Flyweight Factory manages a pool of existing flyweights With the factory, clients don’t create flyweights directly Instead, they call the factory, passing it bits of the intrinsic state of the desired flyweight The factory looks over previously created flyweights and either returns an existing one that matches search criteria or creates a new one if nothing is found
2.2 Implementation
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
2 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
3 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
Trang 74
6
9
4, 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
5 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
3 Example
To better understand how to implement the Flyweight design pattern, let’s look at a sim- ple example about the management of information about cars and the sharing of common information about them to reduce memory usage
This example illustrates the structure of the Flyweight design pattern [5] It focuses on answering these questions:
« What classes does it consist of?
« What roles do these classes play?
« In what way the elements of the pattern are related?
[**
* Flyweight Design Pattern
*
* Intent: 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
*/
struct SharedState
{
std::string brand_;
std::string model_;
std::string color_;
SharedState(const std::string &brand, const std::string &model,
const std::string &color)
: brand_(brand), model_(model), color_(color)
friend std::ostream koperator<<(std::ostream &%os, const SharedState &ss)
{
return os << "[ " << ss.brand_ << " , " << ss.model_
6
Trang 8};
<<", " << s8.color_ << " J";
struct UniqueState
{
std::string owner_;
std::string plates_;
UniqueState(const std::string &owner, const std::string &plates)
owner_(owner), plates_(plates) {
+
friend std::ostream #operator<<(std::ostream &os, const UniqueState &us)
{
return os << "[ " << us.owner_ << ", " << us.plates_ << " |";
+
I;
Next, we implement the Fly Weight Class:
[**
* 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
*/
class Flyweight
{
private:
SharedState *shared_state_;
public:
Flyweight (const SharedState *shared_state) : shared_state_(new
{
}
Flyweight(const Flyweight &other) : shared_state_(new
SharedState(*other.shared_state_))
{
Trang 9}
~Flyweight()
{
delete shared_state_;
}
SharedState *shared_state() const
{
return shared_state_;
}
void Operation(const UniqueState &unique_state) const
{
std::cout << "Flyweight: Displaying shared (" << *shared_state_ << ") and unique (" << unique_state << ") state.";
}
3;
Finally, we implement the FlyWeight Factory class:
[**
* 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
*/
class FlyweightFactory
{
/**
*/
private:
std: :unordered_map<std::string, Flyweight> flyweights_;
[**
* Returns a Flyweight's string hash for a given state
*/
std::string GetKey(const SharedState &ss) const
{
return ss.brand_ + "_" + ss.model_ + "_" + ss.color_;
+
public:
FlyweightFactory(std::initializer_list<SharedState> share states)
{
Trang 10[**
* Returns an existing Flyweight with a given state or creates a new one for (const SharedState &ss : share_states)
{
(this->GetKey(ss), Flyweight (&ss)));
Flyweight GetFlyweight (const SharedState &shared_state)
{
}
if (this->flyweights_.find(key) == this->flyweights_.end())
{
std::cout << "FlyweightFactory: Can't find a flyweight, creating new one.";
Flyweight (&shared_state)));
else
std::cout << "FlyweightFactory: Reusing existing flyweight.\n";
}
return this->flyweights_.at(key) ;
void ListFlyweights() const
{
size_t count = this->flyweights_.size();
std::cout << "\nFlyweightFactory: I have " << count << " flyweights:"; for (std: :pair<std::string, Flyweight> pair : this->flyweights_)
{
std::cout << pair.first << "\n";
void AddCarToPoliceDatabase(
FlyweightFactory &ff, const std::string &plates, const std::string &owner, const std::string &brand, const std::string &model, const std::string &color)
Trang 116T std::cout << "\nClient: Adding a car to database.\n";
m +
So we have used the flyweight pattern to solve this problem, let see how it work:
1 (**
4 */
7 {
10 {"BMW", "M5", "red"}, {"BMW", "X6", "white"}});
tà
19
28
Here is the output of the above code:
10
Trang 12FlyweightFactory: I have 5 flyweights:
BMW_X6_white
Mercedes Benz_C500_red
Mercedes Benz_C300_black
BMW_M5_red
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:
BMW_X1_red
Mercedes Benz_C300_black
BMW_X6_white
Mercedes Benz_C500_red
BMW_M5_red
Here are some popular application of flyweight pattern in real world:
*« Text Processing - A text editor or word processor has to deal with a large number of similar objects like characters The Flyweight pattern allows storing each unique character once and sharing instances of duplicate characters
« File Systems - File systems have to store and retrieve a large number of similar small files like images, audio/video clips Flyweight reduces memory overhead of duplicate files
« Operating System Interfaces - In computer, OS (Operating System) have to display the same icons in different addresses Flyweight pattern helps OS display those by sharing resources with the root files
« Graphics Rendering - Computer games have to render large numbers of similar graph- ical objects like trees, buildings etc Flyweight stores unique graphic objects and shares instances of duplicates for rendering multiple objects in different positions, situations with only one unique object loaded on memory
« Cache Systems - Systems with large in-memory caches like databases use Flyweight to store cached objects more efficiently by sharing duplicates
* Web data managing - Many platforms such as YouTube, Facebook, etc have to store
a large number of data such as images, audio/videos, text, etc Flyweight pattern helps users access their resources in their data pool instead of downloading them to use
11