Приспособленец (англ. flyweight, «легковесный (элемент)») — структурный шаблон проектирования, при котором объект, представляющий себя как уникальный экземпляр в разных местах программы, по факту не является таковым.
Цель
Оптимизация работы с памятью путём предотвращения создания экземпляров элементов, имеющих общую сущность.
Описание
Flyweight используется для уменьшения затрат при работе с большим количеством мелких объектов. При проектировании Flyweight необходимо разделить его свойства на внешние и внутренние. Внутренние свойства всегда неизменны, тогда как внешние могут отличаться в зависимости от места и контекста применения и должны быть вынесены за пределы приспособленца.
Flyweight дополняет шаблон Factory Method таким образом, что при обращении клиента к Factory Method для создания нового объекта ищет уже созданный объект с такими же параметрами, что и у требуемого, и возвращает его клиенту. Если такого объекта нет, то фабрика создаст новый.
Примеры
Пример на Python
Исходный текст на языке Python class Lamp(object): def __init__(self, color): self.color = color class LampFactory: lamps = dict() @staticmethod def get_lamp(color): return LampFactory.lamps.setdefault(color, Lamp(color)) class TreeBranch(object): def __init__(self, branch_number): self.branch_number = branch_number def hang(self, lamp): print(f"Hang ${lamp.color} [${id(lamp)}] lamp on branch ${self.branch_number} [${id(self)}]") class ChristmasTree(object): def __init__(self): self.lamps_hung = 0 self.branches = {} def get_branch(self, number): return self.branches.setdefault(number, TreeBranch(number)) def dress_up_the_tree(self): self.hang_lamp("red", 1) self.hang_lamp("blue", 1) self.hang_lamp("yellow", 1) self.hang_lamp("red", 2) self.hang_lamp("blue", 2) self.hang_lamp("yellow", 2) self.hang_lamp("red", 3) self.hang_lamp("blue", 3) self.hang_lamp("yellow", 3) self.hang_lamp("red", 4) self.hang_lamp("blue", 4) self.hang_lamp("yellow", 4) self.hang_lamp("red", 5) self.hang_lamp("blue", 5) self.hang_lamp("yellow", 5) self.hang_lamp("red", 6) self.hang_lamp("blue", 6) self.hang_lamp("yellow", 6) self.hang_lamp("red", 7) self.hang_lamp("blue", 7) self.hang_lamp("yellow", 7) def hang_lamp(self, color, branch_number): self.get_branch(branch_number).hang(LampFactory.get_lamp(color)) self.lamps_hung += 1 if __name__ == '__main__': ChristmasTree().dress_up_the_tree()Пример на Python (с переопределением конструктора)
Исходный текст на языке Python (с переопределением конструктора) class Lamp(object): __instances = dict() def __new__(cls, color): return cls.__instances.setdefault(color, super().__new__(cls)) def __init__(self, color): self.color = color class TreeBranch(object): def __init__(self, branch_number): self.branch_number = branch_number def hang(self, lamp): print(f"Hang ${lamp.color} [${id(lamp)}] lamp on branch ${self.branch_number} [${id(self)}]") class ChristmasTree(object): def __init__(self): self.lamps_hung = 0 self.branches = {} def get_branch(self, number): return self.branches.setdefault(number, TreeBranch(number)) def dress_up_the_tree(self): for branch in range(1, 8): for color in "red", "blue", "yellow": self.hang_lamp(color, branch) def hang_lamp(self, color, branch_number): self.get_branch(branch_number).hang(Lamp(color)) self.lamps_hung += 1 if __name__ == '__main__': ChristmasTree().dress_up_the_tree()Пример № 1 на Java
Исходный текст на языке Java import java.util.*; public enum FontEffect { BOLD, ITALIC, SUPERSCRIPT, SUBSCRIPT, STRIKETHROUGH } public final class FontData { /** * A weak hash map will drop unused references to FontData. * Values have to be wrapped in WeakReferences, * because value objects in weak hash map are held by strong references. */ private static final WeakHashMap<FontData, WeakReference<FontData>> flyweightData = new WeakHashMap<FontData, WeakReference<FontData>>(); private final int pointSize; private final String fontFace; private final Color color; private final Set<FontEffect> effects; private FontData(int pointSize, String fontFace, Color color, EnumSet<FontEffect> effects) { this.pointSize = pointSize; this.fontFace = fontFace; this.color = color; this.effects = Collections.unmodifiableSet(effects); } public static FontData create(int pointSize, String fontFace, Color color, FontEffect... effects) { EnumSet<FontEffect> effectsSet = EnumSet.noneOf(FontEffect.class); effectsSet.addAll(Arrays.asList(effects)); // We are unconcerned with object creation cost, we are reducing overall memory consumption FontData data = new FontData(pointSize, fontFace, color, effectsSet); if (!flyweightData.containsKey(data)) { flyweightData.put(data, new WeakReference<FontData> (data)); } // return the single immutable copy with the given values return flyweightData.get(data).get(); } @Override public boolean equals(Object obj) { if (obj instanceof FontData) { if (obj == this) { return true; } FontData other = (FontData) obj; return other.pointSize == pointSize && other.fontFace.equals(fontFace) && other.color.equals(color) && other.effects.equals(effects); } return false; } @Override public int hashCode() { return (pointSize * 37 + effects.hashCode() * 13) * fontFace.hashCode(); } // Getters for the font data, but no setters. FontData is immutable. }Пример № 2 на Java
Исходный текст на языке Java public abstract class EnglishCharacter { protected char symbol; protected int width; protected int height; public abstract void printCharacter(); } public class CharacterA extends EnglishCharacter { public CharacterA(){ symbol = 'A'; width = 10; height = 20; } @Override public void printCharacter() { System.out.println("Symbol = " + symbol + " Width = " + width + " Height = " + height); } } public class CharacterB extends EnglishCharacter { public CharacterB(){ symbol = 'B'; width = 20; height = 30; } @Override public void printCharacter() { System.out.println("Symbol = " + symbol + " Width = " + width + " Height = " + height); } } public class CharacterC extends EnglishCharacter { public CharacterC(){ symbol = 'C'; width = 40; height = 50; } @Override public void printCharacter() { System.out.println("Symbol = " + symbol + " Width = " + width + " Height = " + height); } } public class FlyweightFactory { private HashMap<Integer, EnglishCharacter> characters = new HashMap(); public EnglishCharacter getCharacter(int characterCode){ EnglishCharacter character = characters.get(characterCode); if (character == null){ switch (characterCode){ case 1 : { character = new CharacterA(); break; } case 2 : { character = new CharacterB(); break; } case 3 : { character = new CharacterC(); break; } } characters.put(characterCode, character); } return character; } } /* * Класс, показывающий работу шаблона проектирования "Приспособленец". * */ public class Application { public static void main (String [] args){ FlyweightFactory factory = new FlyweightFactory(); int [] characterCodes = {1,2,3}; for (int nextCode : characterCodes){ EnglishCharacter character = factory.getCharacter(nextCode); character.printCharacter(); } } }Пример на C#
Исходный текст на языке C# using System; using System.Collections; namespace Flyweight { class MainApp { static void Main() { // Build a document with text string document = "AAZZBBZB"; char[] chars = document.ToCharArray(); CharacterFactory f = new CharacterFactory(); // extrinsic state int pointSize = 10; // For each character use a flyweight object foreach (char c in chars) { pointSize++; Character character = f.GetCharacter(c); character.Display(pointSize); } // Wait for user Console.Read(); } } // "FlyweightFactory" class CharacterFactory { private Hashtable characters = new Hashtable(); public Character GetCharacter(char key) { // Uses "lazy initialization" Character character = characters[key] as Character; if (character == null) { switch (key) { case 'A': character = new CharacterA(); break; case 'B': character = new CharacterB(); break; //... case 'Z': character = new CharacterZ(); break; } characters.Add(key, character); } return character; } } // "Flyweight" abstract class Character { protected char symbol; protected int width; protected int height; protected int ascent; protected int descent; protected int pointSize; public virtual void Display(int pointSize) { this.pointSize = pointSize; Console.WriteLine(this.symbol + " (pointsize " + this.pointSize + ")"); } } // "ConcreteFlyweight" class CharacterA : Character { // Constructor public CharacterA() { this.symbol = 'A'; this.height = 100; this.width = 120; this.ascent = 70; this.descent = 0; } } // "ConcreteFlyweight" class CharacterB : Character { // Constructor public CharacterB() { this.symbol = 'B'; this.height = 100; this.width = 140; this.ascent = 72; this.descent = 0; } } // ... C, D, E, etc. // "ConcreteFlyweight" class CharacterZ : Character { // Constructor public CharacterZ() { this.symbol = 'Z'; this.height = 100; this.width = 100; this.ascent = 68; this.descent = 0; } } }Пример на C++
Исходный текст на языке C++ #include <map> #include <iostream> #include <memory> // "Flyweight" class Character { public: virtual ~Character() = default; virtual void display() const = 0; protected: char mSymbol; int mWidth; int mHeight; int mAscent; int mDescent; int mPointSize; }; // "ConcreteFlyweight" class ConcreteCharacter : public Character { public: // Constructor ConcreteCharacter( char aSymbol, int aPointSize ) { mSymbol = aSymbol; mWidth = 120; mHeight = 100; mAscent = 70; mDescent = 0; mPointSize = aPointSize; } // from Character virtual void display() const { std::cout << mSymbol << " ( PointSize " << mPointSize << " ) "; } }; // "FlyweightFactory" template < const int POINT_SIZE > class CharacterFactory { public: const Character& getCharacter( char aKey ) { // Uses "lazy initialization" Characters::const_iterator it = mCharacters.find( aKey ); if ( mCharacters.end() == it ) { mCharacters[aKey] = std::make_unique<const ConcreteCharacter>(aKey, POINT_SIZE); return *mCharacters[aKey]; } else { return *it->second; } } private: using Characters = std::map < char, std::unique_ptr<const Character> >; Characters mCharacters; }; int main(){ std::string document = "AAZZBBZB"; CharacterFactory<12> characterFactory; for (auto it :document){ auto&& character = characterFactory.getCharacter( it ); character.display(); } return 0; }Пример на PHP5
Исходный текст на языке PHP <?php // "FlyweightFactory" class CharacterFactory { private $characters = array(); public function GetCharacter($key) { // Uses "lazy initialization" if (!array_key_exists($key, $this->characters)) { switch ($key) { case 'A': $this->characters[$key] = new CharacterA(); break; case 'B': $this->characters[$key] = new CharacterB(); break; //... case 'Z': $this->characters[$key] = new CharacterZ(); break; } } return $this->characters[$key]; } } // "Flyweight" abstract class Character { protected $symbol; protected $width; protected $height; protected $ascent; protected $descent; protected $pointSize; public abstract function Display($pointSize); } // "ConcreteFlyweight" class CharacterA extends Character { // Constructor public function __construct() { $this->symbol = 'A'; $this->height = 100; $this->width = 120; $this->ascent = 70; $this->descent = 0; } public function Display($pointSize) { $this->pointSize = $pointSize; print ($this->symbol." (pointsize ".$this->pointSize.")"); } } // "ConcreteFlyweight" class CharacterB extends Character { // Constructor public function __construct() { $this->symbol = 'B'; $this->height = 100; $this->width = 140; $this->ascent = 72; $this->descent = 0; } public function Display($pointSize) { $this->pointSize = $pointSize; print($this->symbol." (pointsize ".$this->pointSize.")"); } } // ... C, D, E, etc. // "ConcreteFlyweight" class CharacterZ extends Character { // Constructor public function __construct() { $this->symbol = 'Z'; $this->height = 100; $this->width = 100; $this->ascent = 68; $this->descent = 0; } public function Display($pointSize) { $this->pointSize = $pointSize; print($this->symbol." (pointsize ".$this->pointSize.")"); } } $document="AAZZBBZB"; // Build a document with text $chars=str_split($document); print_r($chars); $f = new CharacterFactory(); // extrinsic state $pointSize = 0; // For each character use a flyweight object foreach ($chars as $key) { $pointSize++; $character = $f->GetCharacter($key); $character->Display($pointSize); } ?>Пример на VB.NET
Исходный текст на VB.NET Imports System.Collections Namespace Flyweight Class Program Shared Sub Main() ' Build a document with text Dim document As String = "AAZZBBZB" Dim chars As Char() = document.ToCharArray() Dim f As New CharacterFactory() ' extrinsic state Dim pointSize As Integer = 10 ' For each character use a flyweight object For Each c As Char In chars pointSize += 1 Dim character As Character = f.GetCharacter(c) character.Display(pointSize) Next ' Wait for user Console.Read() End Sub End Class ' "FlyweightFactory" Class CharacterFactory Private characters As New Hashtable() Public Function GetCharacter(ByVal key As Char) As Character ' Uses "lazy initialization" Dim character As Character = TryCast(characters(key), Character) If character Is Nothing Then Select Case key Case "A"c character = New CharacterA() Exit Select Case "B"c character = New CharacterB() Exit Select '... Case "Z"c character = New CharacterZ() Exit Select End Select characters.Add(key, character) End If Return character End Function End Class ' "Flyweight" MustInherit Class Character Protected symbol As Char Protected width As Integer Protected height As Integer Protected ascent As Integer Protected descent As Integer Protected pointSize As Integer Public MustOverride Sub Display(ByVal pointSize As Integer) End Class ' "ConcreteFlyweight" Class CharacterA Inherits Character ' Constructor Public Sub New() Me.symbol = "A"c Me.height = 100 Me.width = 120 Me.ascent = 70 Me.descent = 0 End Sub Public Overrides Sub Display(ByVal pointSize As Integer) Me.pointSize = pointSize Console.WriteLine(Me.symbol & " (pointsize " & Me.pointSize & ")") End Sub End Class ' "ConcreteFlyweight" Class CharacterB Inherits Character ' Constructor Public Sub New() Me.symbol = "B"c Me.height = 100 Me.width = 140 Me.ascent = 72 Me.descent = 0 End Sub Public Overrides Sub Display(ByVal pointSize As Integer) Me.pointSize = pointSize Console.WriteLine(Me.symbol & " (pointsize " & Me.pointSize & ")") End Sub End Class ' ... C, D, E, etc. ' "ConcreteFlyweight" Class CharacterZ Inherits Character ' Constructor Public Sub New() Me.symbol = "Z"c Me.height = 100 Me.width = 100 Me.ascent = 68 Me.descent = 0 End Sub Public Overrides Sub Display(ByVal pointSize As Integer) Me.pointSize = pointSize Console.WriteLine(Me.symbol & " (pointsize " & Me.pointSize & ")") End Sub End Class End NamespaceПример на Ruby
Исходный текст на языке Ruby # Объект Приспособленец class Lamp attr_reader :color #attr_reader makes color attribute available outside #of the class by calling .color on a Lamp instance def initialize(color) @color = color end end class TreeBranch def initialize(branch_number) @branch_number = branch_number end def hang(lamp) puts "Hang #{lamp.color} lamp on branch #{@branch_number}" end end # Flyweight Factory class LampFactory def initialize @lamps = {} end def find_lamp(color) if @lamps.has_key?(color) # if the lamp already exists, reference it instead of creating a new one lamp = @lamps[color] else lamp = Lamp.new(color) @lamps[color] = lamp end lamp end def total_number_of_lamps_made @lamps.size end end class ChristmasTree def initialize @lamp_factory = LampFactory.new @lamps_hung = 0 dress_up_the_tree end def hang_lamp(color, branch_number) TreeBranch.new(branch_number).hang(@lamp_factory.find_lamp(color)) @lamps_hung += 1 end def dress_up_the_tree hang_lamp('red', 1) hang_lamp('blue', 1) hang_lamp('yellow', 1) hang_lamp('red', 2) hang_lamp('blue', 2) hang_lamp('yellow', 2) hang_lamp('red', 3) hang_lamp('blue', 3) hang_lamp('yellow', 3) hang_lamp('red', 4) hang_lamp('blue', 4) hang_lamp('yellow', 4) hang_lamp('red', 5) hang_lamp('blue', 5) hang_lamp('yellow', 5) hang_lamp('red', 6) hang_lamp('blue', 6) hang_lamp('yellow', 6) hang_lamp('red', 7) hang_lamp('blue', 7) hang_lamp('yellow', 7) puts "Made #{@lamp_factory.total_number_of_lamps_made} total lamps" end endСимволы на Smalltalk
Символы в Smalltalk практически идентичны «обычным строкам», но не порождаются каждый раз заново. Два идентичных символа на самом деле всегда являются одним и тем же экземпляром класса Symbol, тогда как две идентичные строки могут быть разными экземплярами класса String.