Flyweight
The Flyweight pattern is a structural design pattern that aims to minimize memory usage or computational costs by sharing as much data as possible between similar objects. It achieves this by separating the object state into intrinsic and extrinsic parts. Intrinsic state is shared and immutable, held within the flyweight object itself, while extrinsic state is unique to each object and passed to the flyweight when needed.
This pattern is particularly useful when dealing with a large number of objects that contain redundant information. By sharing the common, intrinsic state, you significantly reduce the memory footprint. It’s often employed in applications like text editors, graphics editors, or game development, where numerous similar objects (characters, graphical elements, etc.) need to be managed efficiently.
Usage
- Text Editors/Word Processors: Representing characters in a document. Each character might have a different font, size, and color (extrinsic state), while the glyph data for the character itself is shared (intrinsic state).
- Game Development: Managing game entities like trees, bushes, or rocks. Many instances of these entities might share the same visual model and properties (intrinsic state), while their position, rotation, and state (alive/destroyed) are specific to each instance (extrinsic state).
- Database Connection Pooling: Sharing database connections across multiple requests. The connection details are intrinsic, while the specific query and result set are extrinsic.
- Image Sprites: In web development, combining many small images into a single larger image (sprite) and using CSS to display only the required portion. The sprite image is intrinsic, and its position and size within the page is extrinsic.
Examples
-
Java’s
StringPool: Java internally uses a flyweight-like mechanism with theStringpool. When you create a string literal, the JVM first checks if a string with the same value already exists in the pool. If it does, it returns a reference to the existing string; otherwise, it creates a new one and adds it to the pool. This avoids creating duplicate string objects with identical content, saving memory. -
React’s
useMemohook: While not a direct implementation of the Flyweight pattern,useMemoserves a similar purpose by memoizing the result of a function. If the dependencies of the function remain unchanged between renders,useMemoreturns the cached result instead of re-executing the function. This shares computational effort and the resulting object between render cycles, effectively making it a lightweight, shared resource.
Specimens
14 implementationsThe Flyweight pattern aims to minimize memory usage by sharing objects that are similar. It separates an object’s state into intrinsic (shared) and extrinsic (unique) parts. Intrinsic state is stored in the flyweight object, while extrinsic state is passed to the flyweight when needed. This is particularly useful when dealing with a large number of similar objects.
This Dart example demonstrates the Flyweight pattern with Character as the flyweight. The Character stores the intrinsic character itself (the glyph). Text holds references to these shared Character objects and manages the extrinsic state – the color and position of each character instance. The CharacterFactory ensures only one instance of each character exists, providing the flyweight functionality. This approach is idiomatic Dart due to its object-oriented nature and use of classes and factories for managing object creation and state.
// Flyweight interface
abstract class Character {
void display(String context, int x, int y);
}
// Concrete Flyweight
class ConcreteCharacter implements Character {
final String character;
ConcreteCharacter(this.character);
@override
void display(String context, int x, int y) {
print('Character: $character, Context: $context, Position: ($x, $y)');
}
}
// Flyweight Factory
class CharacterFactory {
final Map<String, Character> _characters = {};
Character getCharacter(String character) {
if (!_characters.containsKey(character)) {
_characters[character] = ConcreteCharacter(character);
}
return _characters[character]!;
}
}
// Flyweight User (Client)
class Text {
final CharacterFactory characterFactory;
final List<Character> characters = [];
final List<String> contexts = [];
final List<int> xPositions = [];
final List<int> yPositions = [];
Text(this.characterFactory);
void addCharacter(String character, String context, int x, int y) {
final charFlyweight = characterFactory.getCharacter(character);
characters.add(charFlyweight);
contexts.add(context);
xPositions.add(x);
yPositions.add(y);
}
void display() {
for (int i = 0; i < characters.length; i++) {
characters[i].display(contexts[i], xPositions[i], yPositions[i]);
}
}
}
void main() {
final factory = CharacterFactory();
final text = Text(factory);
text.addCharacter('a', 'sentence1', 10, 20);
text.addCharacter('b', 'sentence1', 30, 20);
text.addCharacter('a', 'sentence2', 10, 40);
text.addCharacter('c', 'sentence2', 50, 40);
text.display();
}
The Flyweight pattern aims to minimize memory usage by sharing objects that are identical or similar. It separates an object’s state into intrinsic (shared) and extrinsic (unique) parts. Intrinsic state is stored in the flyweight object, while extrinsic state is passed to the flyweight when needed. This example models trees in a forest, where many trees might share the same type and color (intrinsic), but have unique positions (extrinsic). The TreeType is the flyweight, and Forest manages the extrinsic state for each tree instance. Scala’s immutability and case classes make it well-suited for this pattern, as flyweights can be safely shared.
// Define the intrinsic state (Flyweight)
case class TreeType(species: String, color: String)
// Define the context (Forest) - manages extrinsic state
case class Tree(treeType: TreeType, x: Int, y: Int)
object Forest {
private var treeTypes: Map[String, TreeType] = Map.empty
def getTreeType(species: String, color: String): TreeType = {
treeTypes.getOrElse(
(species, color).toString,
TreeType(species, color)
)
}
def createTree(species: String, color: String, x: Int, y: Int): Tree = {
val treeType = getTreeType(species, color)
Tree(treeType, x, y)
}
def main(args: Array[String]): Unit = {
val tree1 = Forest.createTree("Oak", "Green", 10, 20)
val tree2 = Forest.createTree("Oak", "Green", 30, 40) // Shares the same TreeType as tree1
val tree3 = Forest.createTree("Pine", "Dark Green", 50, 60)
val tree4 = Forest.createTree("Pine", "Dark Green", 70, 80) // Shares the same TreeType as tree3
println(s"Tree 1: ${tree1}")
println(s"Tree 2: ${tree2}")
println(s"Tree 3: ${tree3}")
println(s"Tree 4: ${tree4}")
//Demonstrate that tree1 and tree2 share the same TreeType instance
println(s"Tree 1 and Tree 2 have the same TreeType instance: ${tree1.treeType == tree2.treeType}")
println(s"Tree 3 and Tree 4 have the same TreeType instance: ${tree3.treeType == tree4.treeType}")
}
}
The Flyweight pattern aims to minimize memory usage by sharing objects that are similar. It achieves this by separating an object’s state into intrinsic (shared) and extrinsic (unique) parts. Intrinsic state is stored within the flyweight object, while extrinsic state is passed to the flyweight when needed. This example demonstrates flyweights for representing simple geometric shapes (circles). The Circle class is the flyweight, storing the color (intrinsic). The x and y coordinates are extrinsic and passed to the draw method. This is idiomatic PHP as it leverages classes and methods for organization, and avoids unnecessary object creation by reusing the Circle object with different coordinates.
<?php
/**
* Flyweight Interface
*/
interface CircleInterface {
public function draw(float $x, float $y): void;
}
/**
* Concrete Flyweight: Circle
*/
class Circle implements CircleInterface {
private string $color;
public function __construct(string $color) {
$this->color = $color;
}
public function draw(float $x, float $y): void {
echo "Drawing a {$this->color} circle at ({$x}, {$y}).\n";
}
public function getColor(): string {
return $this->color;
}
}
/**
* Flyweight Factory
*/
class CircleFactory {
private array $circles = [];
public function getCircle(string $color): CircleInterface {
$colorKey = $color;
if (!isset($this->circles[$colorKey])) {
$this->circles[$colorKey] = new Circle($color);
}
return $this->circles[$colorKey];
}
}
// Usage
$factory = new CircleFactory();
$circle1 = $factory->getCircle('red');
$circle1->draw(10, 10);
$circle2 = $factory->getCircle('blue');
$circle2->draw(20, 20);
$circle3 = $factory->getCircle('red'); // Reuse existing red circle
$circle3->draw(30, 30);
?>
The Flyweight pattern aims to minimize memory usage by sharing common parts of objects. It’s useful when dealing with a large number of similar objects. The pattern separates the object’s state into intrinsic (shared) and extrinsic (unique) components. Intrinsic state is stored in the flyweight object, while extrinsic state is passed as arguments when the flyweight is used.
This Ruby implementation represents trees where many leaves share the same properties (color, type). The Leaf class is the flyweight, storing only the intrinsic color and type. The extrinsic position (x, y coordinates) is passed to the draw method. The Forest class acts as the flyweight factory, managing and reusing Leaf instances. This is idiomatic Ruby due to its flexible object model and use of hashes for efficient storage and retrieval of shared objects.
# Flyweight
class Leaf
attr_reader :color, :type
def initialize(color, type)
@color = color
@type = type
end
def draw(position)
puts "Drawing leaf of type #{@type} with color #{@color} at position #{position}"
end
end
# Flyweight Factory
class Forest
def initialize
@leaves = {}
end
def get_leaf(color, type)
key = [color, type]
@leaves[key] ||= Leaf.new(color, type)
end
end
# Client
forest = Forest.new
positions = [[1, 2], [3, 4], [5, 6], [7, 8]]
positions.each do |pos|
leaf = forest.get_leaf("green", "oak")
leaf.draw(pos)
leaf2 = forest.get_leaf("red", "maple")
leaf2.draw(pos)
end
The Flyweight pattern aims to minimize memory usage by sharing as much data as possible between similar objects. It separates the object’s state into intrinsic (shared) and extrinsic (unique) components. Intrinsic state is stored in the flyweight object, while extrinsic state is passed to the flyweight as needed. This example models trees in a forest. The TreeType is the flyweight, storing shared properties like the tree’s image. Each Tree instance (representing a tree in the forest) holds only the unique extrinsic state – its position (x, y coordinates). This avoids storing redundant image data for every tree. The Swift implementation leverages structs for immutability and value semantics, fitting the pattern’s intent to share data efficiently.
// Flyweight
struct TreeType {
let name: String
let image: String // Shared resource
func draw(x: Int, y: Int) {
print("Drawing \(name) at (\(x), \(y)) with image: \(image)")
}
}
// Client / Context
class Tree {
let type: TreeType
let x: Int
let y: Int
init(type: TreeType, x: Int, y: Int) {
self.type = type
self.x = x
self.y = y
}
func draw() {
type.draw(x: x, y: y)
}
}
// Flyweight Factory
class Forest {
private var treeTypes: [String: TreeType] = [:]
func getTreeType(name: String, image: String) -> TreeType {
if let treeType = treeTypes[name] {
return treeType
} else {
let newTreeType = TreeType(name: name, image: image)
treeTypes[name] = newTreeType
return newTreeType
}
}
func plantTree(name: String, image: String, x: Int, y: Int) -> Tree {
let treeType = getTreeType(name: name, image: image)
return Tree(type: treeType, x: x, y: y)
}
}
// Usage
let forest = Forest()
let tree1 = forest.plantTree(name: "Oak", image: "oak.png", x: 10, y: 20)
let tree2 = forest.plantTree(name: "Pine", image: "pine.png", x: 30, y: 40)
let tree3 = forest.plantTree(name: "Oak", image: "oak.png", x: 50, y: 60) // Reuses Oak type
tree1.draw()
tree2.draw()
tree3.draw()
The Flyweight pattern aims to minimize memory usage by sharing objects that are similar. It separates the object’s state into intrinsic (shared) and extrinsic (unique) parts. Intrinsic state is stored in the flyweight object, while extrinsic state is passed as a parameter when the flyweight is used. This example demonstrates this with Tree objects. Leaf represents the flyweight, storing a common color. Node represents the context, holding the unique position (x, y) of each leaf. Kotlin’s data classes and immutability align well with the pattern’s need for shared, unchanging state.
// Flyweight Interface
interface Leaf {
fun display(x: Int, y: Int)
}
// Concrete Flyweight
data class SimpleLeaf(private val color: String) : Leaf {
override fun display(x: Int, y: Int) {
println("Leaf at ($x, $y) with color $color")
}
}
// Flyweight Factory
object LeafFactory {
private val leaves = mutableMapOf<String, Leaf>()
fun getLeaf(color: String): Leaf {
return leaves.getOrPut(color) { SimpleLeaf(color) }
}
}
// Context
data class Node(val x: Int, val y: Int) {
fun render(leaf: Leaf) {
leaf.display(x, y)
}
}
fun main() {
val leaf1 = LeafFactory.getLeaf("red")
val leaf2 = LeafFactory.getLeaf("green")
val leaf3 = LeafFactory.getLeaf("red") // Reuses the existing "red" leaf
val node1 = Node(10, 20)
val node2 = Node(30, 40)
val node3 = Node(15, 25)
node1.render(leaf1)
node2.render(leaf2)
node3.render(leaf3)
}
The Flyweight pattern aims to minimize memory usage by sharing common parts of objects. It’s useful when dealing with a large number of similar objects. The pattern separates the object’s state into intrinsic (shared) and extrinsic (unique) components. The Flyweight interface defines a method to operate on the extrinsic state. A Flyweight factory manages the shared objects, creating them only if they don’t already exist.
This Rust implementation represents trees with leaves. The Leaf struct holds the intrinsic state (color), while the position is extrinsic and passed to the display method. The LeafFactory acts as the flyweight factory, caching and returning existing Leaf instances. Rust’s ownership and borrowing system, coupled with the Rc smart pointer, naturally supports shared ownership of the intrinsic state, making it well-suited for Flyweight.
use std::rc::Rc;
use std::collections::HashMap;
// Intrinsic state
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
enum Color {
Red,
Green,
Blue,
}
// Flyweight interface
trait Tree {
fn display(&self, x: i32, y: i32);
}
// Concrete Flyweight
struct Leaf {
color: Color,
}
impl Leaf {
fn new(color: Color) -> Self {
Leaf { color }
}
}
impl Tree for Leaf {
fn display(&self, x: i32, y: i32) {
println!("Leaf at ({}, {}) with color {:?}", x, y, self.color);
}
}
// Flyweight Factory
struct LeafFactory {
leaves: HashMap<Color, Rc<Leaf>>,
}
impl LeafFactory {
fn new() -> Self {
LeafFactory {
leaves: HashMap::new(),
}
}
fn get_leaf(&mut self, color: Color) -> Rc<Leaf> {
if let Some(leaf) = self.leaves.get(&color) {
leaf.clone()
} else {
let leaf = Rc::new(Leaf::new(color));
self.leaves.insert(color, leaf.clone());
leaf
}
}
}
fn main() {
let mut factory = LeafFactory::new();
let leaf1 = factory.get_leaf(Color::Red);
let leaf2 = factory.get_leaf(Color::Green);
let leaf3 = factory.get_leaf(Color::Red); // Reuse existing Red leaf
leaf1.display(10, 10);
leaf2.display(20, 20);
leaf3.display(30, 30);
println!("Number of leaves created: {}", factory.leaves.len());
}
The Flyweight pattern aims to minimize memory usage or computational expenses by sharing as much data as possible between similar objects. It achieves this by separating the object’s state into intrinsic (shared) and extrinsic (unique) components. Intrinsic state is stored within the flyweight object, while extrinsic state is passed as arguments to the flyweight’s methods.
This Go implementation represents trees where many leaves share the same properties (color, type). Leaf is the flyweight interface, and ConcreteLeaf holds the intrinsic state. Forest acts as the flyweight factory, managing the shared ConcreteLeaf instances. Each tree (represented by coordinates) receives the extrinsic state (leaf type) when Draw is called, demonstrating how the pattern reduces memory by reusing leaf objects. Go’s use of interfaces and maps for efficient lookup aligns well with the Flyweight pattern’s principles.
package main
import "fmt"
// Flyweight interface
type Leaf interface {
Draw(x, y int, leafType string)
}
// ConcreteFlyweight
type ConcreteLeaf struct {
color string
}
func (l *ConcreteLeaf) Draw(x, y int, leafType string) {
fmt.Printf("Drawing a %s leaf at (%d, %d) with color %s\n", leafType, x, y, l.color)
}
// Flyweight Factory
type Forest struct {
leafs map[string]Leaf
}
func NewForest() *Forest {
return &Forest{
leafs: make(map[string]Leaf),
}
}
func (f *Forest) GetLeaf(leafType string) Leaf {
if _, ok := f.leafs[leafType]; !ok {
switch leafType {
case "oak":
f.leafs[leafType] = &ConcreteLeaf{color: "green"}
case "maple":
f.leafs[leafType] = &ConcreteLeaf{color: "red"}
default:
return nil // Or handle unknown leaf types
}
}
return f.leafs[leafType]
}
func main() {
forest := NewForest()
forest.GetLeaf("oak").Draw(10, 20, "oak")
forest.GetLeaf("maple").Draw(30, 40, "maple")
forest.GetLeaf("oak").Draw(50, 60, "oak") // Reuses the existing oak leaf
forest.GetLeaf("birch").Draw(70, 80, "birch") // Returns nil, as birch is not defined
}
The Flyweight pattern aims to minimize memory usage by sharing as much data as possible between similar objects. It separates the object’s state into intrinsic (shared) and extrinsic (unique) components. Intrinsic state is stored in the flyweight object, while extrinsic state is passed to the flyweight as needed. This example represents trees in a forest, where leaf color and position are unique (extrinsic), but the tree type (e.g., oak, pine) is shared (intrinsic). The TreeType struct holds the intrinsic state, and the Tree struct represents a concrete tree instance, receiving extrinsic state (x, y coordinates) at runtime. C’s struct-based approach naturally lends itself to separating data this way.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// Intrinsic State - Shared
typedef struct {
char *type;
char *description;
} TreeType;
// Flyweight Factory
typedef struct {
TreeType *tree_types[5]; // Limited number of tree types
int num_tree_types;
} Forest;
Forest *create_forest() {
Forest *forest = (Forest *)malloc(sizeof(Forest));
forest->num_tree_types = 0;
return forest;
}
TreeType *get_tree_type(Forest *forest, const char *type) {
for (int i = 0; i < forest->num_tree_types; i++) {
if (strcmp(forest->tree_types[i]->type, type) == 0) {
return forest->tree_types[i];
}
}
// Create a new tree type if it doesn't exist
TreeType *new_type = (TreeType *)malloc(sizeof(TreeType));
new_type->type = strdup(type);
new_type->description = strdup("Generic tree"); // Default description
forest->tree_types[forest->num_tree_types++] = new_type;
return new_type;
}
// Extrinsic State - Unique to each tree
typedef struct {
TreeType *type;
int x, y;
} Tree;
void draw_tree(Tree *tree) {
printf("Drawing a %s tree at (%d, %d)\n", tree->type->type, tree->x, tree->y);
}
int main() {
Forest *forest = create_forest();
TreeType *oak = get_tree_type(forest, "Oak");
TreeType *pine = get_tree_type(forest, "Pine");
Tree tree1 = {oak, 10, 20};
Tree tree2 = {pine, 30, 40};
Tree tree3 = {oak, 50, 60};
draw_tree(&tree1);
draw_tree(&tree2);
draw_tree(&tree3);
// Clean up (important!)
for (int i = 0; i < forest->num_tree_types; i++) {
free(forest->tree_types[i]->type);
free(forest->tree_types[i]->description);
free(forest->tree_types[i]);
}
free(forest);
return 0;
}
The Flyweight pattern aims to minimize memory usage by sharing common parts of objects. It’s particularly useful when dealing with a large number of similar objects. The pattern separates the object’s intrinsic state (shared) from its extrinsic state (unique to each instance). Flyweights represent the shared intrinsic state, while clients manage the unique extrinsic state.
This C++ example demonstrates the Flyweight pattern with a simple Character class. The CharacterFactory ensures only one instance of each character (intrinsic state) is created. The Context class holds the font, color, and position (extrinsic state) for each character’s usage. This approach avoids storing redundant character data, saving memory when rendering many characters. The use of a std::map in the factory is idiomatic for efficient lookup and storage of the flyweights.
#include <iostream>
#include <map>
#include <string>
// Intrinsic State: Shared among many objects
class Character {
public:
char glyph;
Character(char glyph) : glyph(glyph) {}
void display(int x, int y, std::string color) {
std::cout << "Character: " << glyph << ", Position: (" << x << ", " << y << "), Color: " << color << std::endl;
}
};
// Flyweight Factory: Manages Flyweight objects
class CharacterFactory {
public:
std::map<char, Character*> characters;
Character* getCharacter(char key) {
if (characters.find(key) == characters.end()) {
characters[key] = new Character(key);
}
return characters[key];
}
};
// Context: Holds extrinsic state and uses Flyweights
class Context {
public:
Character* character;
int x;
int y;
std::string color;
Context(Character* character, int x, int y, std::string color)
: character(character), x(x), y(y), color(color) {}
void display() {
character->display(x, y, color);
}
};
int main() {
CharacterFactory factory;
Context context1(factory.getCharacter('A'), 10, 20, "red");
Context context2(factory.getCharacter('B'), 30, 40, "green");
Context context3(factory.getCharacter('A'), 50, 60, "blue");
context1.display();
context2.display();
context3.display();
// Clean up (important in C++)
for (auto const& [key, val] : factory.characters) {
delete val;
}
return 0;
}
The Flyweight pattern aims to minimize memory usage by sharing as much data as possible between similar objects. It achieves this by separating an object’s state into intrinsic (shared, immutable) and extrinsic (unique, passed as arguments) components. The Flyweight interface defines the methods that accept extrinsic state. ConcreteFlyweight holds the intrinsic state. FlyweightFactory manages the creation and sharing of flyweight instances.
This C# implementation uses a dictionary within the FlyweightFactory to store and reuse existing flyweights based on their key. The IntrinsicState is a simple string, representing shared data. The Client code holds the extrinsic state (coordinates) and calls the flyweight’s methods, passing in this unique context. This approach is idiomatic C# due to its use of interfaces, a factory pattern, and dictionaries for efficient object management.
// Flyweight Interface
public interface IFlyweight
{
void Operation(int x, int y);
}
// Concrete Flyweight
public class ConcreteFlyweight : IFlyweight
{
private readonly string intrinsicState;
public ConcreteFlyweight(string intrinsicState)
{
this.intrinsicState = intrinsicState;
}
public void Operation(int x, int y)
{
Console.WriteLine($"Flyweight with intrinsic state '{intrinsicState}' at coordinates: ({x}, {y})");
}
}
// Flyweight Factory
public class FlyweightFactory
{
private readonly Dictionary<string, IFlyweight> flyweights = new();
public IFlyweight GetFlyweight(string key)
{
if (!flyweights.ContainsKey(key))
{
flyweights[key] = new ConcreteFlyweight(key);
}
return flyweights[key];
}
}
// Client
public class Client
{
public static void Main(string[] args)
{
FlyweightFactory factory = new();
IFlyweight flyweight1 = factory.GetFlyweight("A");
IFlyweight flyweight2 = factory.GetFlyweight("B");
IFlyweight flyweight3 = factory.GetFlyweight("A"); // Reuse existing flyweight
flyweight1.Operation(1, 2);
flyweight2.Operation(3, 4);
flyweight3.Operation(5, 6); // Uses the same instance as flyweight1
}
}
The Flyweight pattern aims to minimize memory usage by sharing as much data as possible between similar objects. It achieves this by separating an object’s intrinsic (shared) state from its extrinsic (unique) state. Intrinsic state is stored in the Flyweight object, while extrinsic state is passed as arguments when the Flyweight is used.
This example demonstrates the Flyweight pattern with a simple Tree scenario. Tree objects can have different types (e.g., ‘Oak’, ‘Pine’), which is intrinsic state, and positions, which are extrinsic. The TreeFactory manages the shared Tree instances, creating them only if they don’t already exist. When a tree is rendered, its position is passed as an argument to the draw method. This approach avoids creating numerous identical Tree objects, saving memory, especially when dealing with a large forest. The use of a factory and a simple object literal for the tree type aligns with common JavaScript practices.
// Tree Flyweight
class Tree {
constructor(type) {
this.type = type;
}
draw(x, y) {
console.log(`Drawing a ${this.type} tree at (${x}, ${y})`);
}
}
// Tree Factory
class TreeFactory {
constructor() {
this.trees = {};
}
getTree(type) {
if (!this.trees[type]) {
this.trees[type] = new Tree(type);
}
return this.trees[type];
}
}
// Usage
const factory = new TreeFactory();
const forest = [
{ x: 10, y: 20, type: 'Oak' },
{ x: 15, y: 25, type: 'Pine' },
{ x: 12, y: 18, type: 'Oak' },
{ x: 20, y: 30, type: 'Pine' },
{ x: 8, y: 15, type: 'Oak' },
];
forest.forEach(treeData => {
const tree = factory.getTree(treeData.type);
tree.draw(treeData.x, treeData.y);
});
The Flyweight pattern aims to minimize memory usage by sharing as much data as possible between similar objects. It achieves this by separating an object’s intrinsic (shared, immutable) state from its extrinsic (unique, mutable) state. The intrinsic state is stored in a flyweight object, while the extrinsic state is passed to the flyweight when needed.
This Python example demonstrates the Flyweight pattern with TextFragment as the flyweight. The intrinsic state is the text itself, shared across instances referencing the same text. The extrinsic state – x and y coordinates – are passed to the draw method, making each visual representation unique despite sharing the underlying text data. This is idiomatic Python due to its use of dictionaries for efficient lookup of shared objects and the flexibility of passing arguments to methods.
class TextFragment:
"""
Flyweight class representing a shared text fragment.
"""
def __init__(self, text):
self.text = text
def draw(self, x, y):
"""
Draws the text fragment at the given coordinates.
Extrinsic state is passed as arguments.
"""
print(f"Drawing '{self.text}' at ({x}, {y})")
class TextRenderer:
"""
Manages flyweight objects and provides access to them.
"""
def __init__(self):
self.fragments = {}
def get_fragment(self, text):
"""
Retrieves a text fragment from the cache.
Creates a new one if it doesn't exist.
"""
if text not in self.fragments:
self.fragments[text] = TextFragment(text)
return self.fragments[text]
def render_text(self, text, coordinates):
"""
Renders a string of text using flyweights.
"""
for i, char in enumerate(text):
fragment = self.get_fragment(char)
x = coordinates[0] + i
y = coordinates[1]
fragment.draw(x, y)
if __name__ == "__main__":
renderer = TextRenderer()
renderer.render_text("Hello, world!", (10, 20))
renderer.render_text("Another text", (5, 30))
The Flyweight pattern aims to minimize memory usage by sharing as much data as possible between similar objects. It achieves this by separating an object’s state into intrinsic (shared) and extrinsic (unique) components. Intrinsic state is stored in the flyweight object, while extrinsic state is passed to the flyweight when needed.
This Java example demonstrates the Flyweight pattern with SecuritySector as the flyweight. The sector’s security level (intrinsic state) is shared. The coordinates (extrinsic state) are unique to each instance and passed in during use. SecuritySectorFactory manages the flyweight instances, ensuring only one exists for each security level. This is idiomatic Java due to its use of interfaces (SecuritySector), a factory class (SecuritySectorFactory) for controlled instantiation, and leveraging the immutability of strings for the intrinsic state.
import java.util.HashMap;
import java.util.Map;
interface SecuritySector {
void deploy(int x, int y);
}
class ConcreteSecuritySector implements SecuritySector {
private final String securityLevel;
ConcreteSecuritySector(String securityLevel) {
this.securityLevel = securityLevel;
}
@Override
public void deploy(int x, int y) {
System.out.println("Deploying " + securityLevel + " sector at (" + x + ", " + y + ")");
}
}
class SecuritySectorFactory {
private final Map<String, SecuritySector> sectors = new HashMap<>();
public SecuritySector getSecuritySector(String securityLevel) {
return sectors.computeIfAbsent(securityLevel, level -> new ConcreteSecuritySector(level));
}
}
public class FlyweightDemo {
public static void main(String[] args) {
SecuritySectorFactory factory = new SecuritySectorFactory();
SecuritySector sectorA = factory.getSecuritySector("High");
SecuritySector sectorB = factory.getSecuritySector("Low");
SecuritySector sectorC = factory.getSecuritySector("High"); // Reuse existing "High" sector
sectorA.deploy(10, 20);
sectorB.deploy(30, 40);
sectorC.deploy(50, 60);
}
}