Flyweight Pattern
The Flyweight design pattern is a structural pattern designed to minimize memory usage by sharing as much data as possible with other similar objects. It divides an object's data into two categories: intrinsic state(heavy, constant, and shareable data) and extrinsic state (lightweight, unique, context-specific data)
#include <iostream>
#include <string>
#include <string_view>
#include <unordered_map>
#include <vector>
#include <memory>
#include <shared_mutex>
// 1. INTRINSIC STATE (The Flyweight)
// This class contains heavy, immutable data shared across thousands of instances.
class TreeModel {
public:
TreeModel(std::string name, std::string color, std::vector texture_data)
: name_(std::move(name)), color_(std::move(color)), texture_bytes_(std::move(texture_data)) {
std::cout << "[Cache Miss] Created heavy TreeModel: " << name_ << " (" << color_ << ")\n";
}
void render(double x, double y) const {
std::cout << "Rendering " << name_ << " [" << color_ << "] at coordinates (" << x << ", " << y << ")\n";
}
private:
std::string name_;
std::string color_;
std::vector texture_bytes_; // Simulates heavy mesh/texture data
};
// 2. FLYWEIGHT FACTORY
// Manages the shared pool of flyweights. It ensures objects are safely reused.
class TreeFactory {
public:
// Thread-safe lookup and retrieval using std::shared_ptr
std::shared_ptr get_tree_type(std::string_view name, std::string_view color) {
// Create a unique compound key for our map lookup
std::string key = std::string(name) + "_" + std::string(color);
// Reader lock: Multiple threads can search the cache simultaneously
{
std::shared_lock lock(cache_mutex_);
if (auto it = cache_.find(key); it != cache_.end()) {
return it->second;
}
}
// Writer lock: Only one thread can insert a new model if it's a cache miss
std::unique_lock lock(cache_mutex_);
// Double-checked locking pattern to handle race conditions
auto& ptr = cache_[key];
if (!ptr) {
// Mocking heavy texture bytes allocation
std::vector mock_texture(1024 * 1024, 0xFF);
ptr = std::make_shared(std::string(name), std::string(color), std::move(mock_texture));
}
return ptr;
}
size_t get_pool_size() const {
std::shared_lock lock(cache_mutex_);
return cache_.size();
}
private:
mutable std::shared_mutex cache_mutex_;
std::unordered_map> cache_;
};
// 3. EXTRINSIC STATE & CONTEXT
// Contains unique properties (coordinates) and links back to the shared intrinsic state.
class Tree {
public:
Tree(double x, double y, std::shared_ptr model)
: x_(x), y_(y), model_(std::move(model)) {}
void draw() const {
// Delegates behavior to the intrinsic flyweight object, passing extrinsic context
model_->render(x_, y_);
}
private:
double x_; // Extrinsic state
double y_; // Extrinsic state
std::shared_ptr model_; // Pointer to shared Intrinsic state
};
// 4. CLIENT CODE
class Forest {
public:
void plant_tree(double x, double y, std::string_view name, std::string_view color) {
auto model = factory_.get_tree_type(name, color);
trees_.emplace_back(x, y, std::move(model));
}
void draw() const {
for (const auto& tree : trees_) {
tree.draw();
}
}
size_t total_unique_models() const { return factory_.get_pool_size(); }
size_t total_rendered_trees() const { return trees_.size(); }
private:
TreeFactory factory_;
std::vector trees_;
};
int main() {
Forest forest;
// Planting multiple trees; many share the same type configurations
forest.plant_tree(10.5, 20.3, "Oak", "Green");
forest.plant_tree(15.1, 40.2, "Oak", "Green"); // Reuses the instance above
forest.plant_tree(100.0, 200.0, "Pine", "Dark Green");
forest.plant_tree(50.2, 89.1, "Oak", "Green"); // Reuses the instance above
forest.plant_tree(102.4, 204.8, "Pine", "Dark Green"); // Reuses the instance above
std::cout << "\n--- Forest Statistics ---\n";
std::cout << "Total trees planted: " << forest.total_rendered_trees() << "\n";
std::cout << "Total allocated Flyweight models in RAM: " << forest.total_unique_models() << "\n\n";
std::cout << "--- Rendering Forest ---\n";
forest.draw();
}
Comments
Post a Comment