Приспособленец (шаблон проектирования)
Приспособленец (англ. flyweight, «легковесный (элемент)») — структурный шаблон проектирования, при котором объект, представляющий себя как уникальный экземпляр в разных местах программы, по факту не является таковым.
Приспособленец | |
---|---|
Flyweight | |
Тип | структурный |
Описан в Design Patterns | Да |
Цель
Оптимизация работы с памятью путём предотвращения создания экземпляров элементов, имеющих общую сущность.
Описание
Flyweight используется для уменьшения затрат при работе с большим количеством мелких объектов. При проектировании Flyweight необходимо разделить его свойства на внешние и внутренние. Внутренние свойства всегда неизменны, тогда как внешние могут отличаться в зависимости от места и контекста применения и должны быть вынесены за пределы приспособленца.
Flyweight дополняет шаблон Factory Method таким образом, что при обращении клиента к Factory Method для создания нового объекта ищет уже созданный объект с такими же параметрами, что и у требуемого, и возвращает его клиенту. Если такого объекта нет, то фабрика создаст новый.
Примеры
Пример на Python
class Lamp(object):
def __init__(self, color):
self.color = color
class LampFactory:
lamps = {}
@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("Hang {} [{}] lamp on branch {} [{}]".format(lamp.color, id(lamp), 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()
Пример № 1 на 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
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#
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++
#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 << " )\n";
}
};
// "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
// "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
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
# Объект Приспособленец
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.