Легковаговик (шаблон проєктування)
У програмуванні, легковаговик[1][2](англ. flyweight pattern) — шаблон проєктування, за яким створюється об'єкт, що мінімізує використання пам'яті розподіляючи стільки даних скільки це можливо між іншими подібними об'єктами; це спосіб використання великої кількості об'єктів, коли просте повторювання їх представлення зайняло б неприпустиму кількість пам'яті. Часто деякі частини, що зберігають стан об'єктів можуть бути спільними, і загальною практикою є тримати їх у деякий зовнішній структурі даних і передавати їх до об'єктів тимчасово коли вони використовуються.
Класичним прикладом застосування шаблону легковаговик це структури даних для графічного представлення символів при відображенні тексту. Для цього необхідно буде мати, для кожного символу в документі, об'єкт гліфу що містить контури шрифту, метрику і інші дані про форматування, але це буде займати сотні або тисячі байт для кожного символу. Замість представлення таким чином кожного символу, там можна задати посилання на легкий, щодо використання пам'яті, об'єкт із гліфом для кожного екземпляру однакового символу в документі; і в результуючому об'єкті доведеться зберігати лише тільки позицію кожного символу (в документі і/або на сторінці).
Іншим прикладом застосування цього шаблону є метод стиснення текстових рядків шляхом інтернування.
Призначення
Використовується для ефективної підтримки (в першу чергу для зменшення затрат пам'яті) великої кількості дрібних об'єктів.
Опис
Шаблон Легковаговик (Flyweight) використовує загальнодоступний легкий об'єкт (flyweight, легковаговик), який одночасно може використовуватися у великій кількості контекстів. Стан цього об'єкта поділяється на внутрішній, що містить інформацію, незалежну від контексту, і зовнішній, який залежить або змінюється разом з контекстом легковаговика. Об'єкти клієнтів відповідають за передачу зовнішнього стану легковаговика, коли йому це необхідно.
Переваги
- Зменшує кількість об'єктів, що підлягають обробці.
- Зменшує вимоги до пам'яті.
Недоліки
- Переміщення стану поза об'єктом розбиває інкапсуляцію та може бути менш ефективним, ніж збереження власного поля
Застосування
Шаблон Легковаговик можна використовувати коли:
- В програмі використовується велика кількість об'єктів.
- Затрати на збереження високі через велику кількість об'єктів.
- Більшість станів об'єктів можна зробити зовнішніми.
- Велика кількість груп об'єктів може бути замінена відносно малою кількістю загальнодоступних об'єктів, однократно видаливши зовнішній стан.
- Програма не залежить від ідентичності об'єктів. Оскільки об'єкти-легковаговики можуть використовуватися колективно, то тести на ідентичність будуть повертати значення "істина" ("true") для концептуально різних об'єктів.
Діаграма UML
Реалізація
C++
#include <istream>
#include <string>
#include <vector>
#include <map>
using namespace std;
// моделює зображення
// для простоти розуміння зображення подане пустою структурою
struct Image
{
// тут міститься щось важке
static Image load(string path)
{
return Image();
}
};
// базовий клас для персонажів
class Unit
{
protected:
string name;
float health;
Image picture;
};
// неправильний варіант для наглядності
struct Goblin : public Unit
{
Goblin()
{
name = "Goblin";
health = 8;
picture = Image::load("Goblin.jpg");
}
};
struct Dragon : public Unit
{
Dragon()
{
name = "Dragon";
health = 800;
picture = Image::load("Dragon.jpg");
}
};
// Легковик в дії
// Сховище для зображень
class UnitImagesFactory
{
private:
static map<string, Image> Images;
static UnitImagesFactory uif;
UnitImagesFactory()
{
Images["Dragon"] = Image::load("Dragon.jpg");
Images["Goblin"] = Image::load("Goblin.jpg");
}
virtual ~UnitImagesFactory() {}
public:
static Image create_dragon_image()
{
return Images["Dragon"];
}
static Image create_goblin_image()
{
return Images["Goblin"];
}
};
map<string, Image> UnitImagesFactory::Images;
UnitImagesFactory UnitImagesFactory::uif;
// Конструктори гобліна і дракона дещо змінені для використання нашої фабрики
struct BetterGoblin : public Unit
{
BetterGoblin()
{
name = "Goblin";
health = 8;
picture = UnitImagesFactory::create_goblin_image();
}
};
struct BetterDragon : Unit
{
BetterDragon()
{
name = "Dragon";
health = 800;
picture = UnitImagesFactory::create_dragon_image();
}
};
void main()
{
// повільне створення
// зображення грузиться для кожного юніта
vector<Unit *> units;
for (int i = 0; i < 15; i++)
{
units.push_back(new Dragon());
}
for (int i = 0; i < 50; i++)
{
units.push_back(new Goblin());
}
// швидке створення
// готове зображення надається усім юнітам
vector<Unit *> better_units;
for (int i = 0; i < 15; i++)
{
better_units.push_back(new BetterDragon());
}
for (int i = 0; i < 50; i++)
{
better_units.push_back(new BetterGoblin());
}
// очищаємо динамічну пам'ять
for (int i = 0; i < 65; i++)
{
delete units[i];
}
for (int i = 0; i < 65; i++)
{
delete better_units[i];
}
}
C#
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Collections;
namespace lab2_v3
{
class Program
{
static void Main()
{
string context="";
LineFactory fac = new LineFactory();
fac.GetLine(2, "red").Draw(context,14,15);
fac.GetLine(2, "red").Draw(context, 14, 15);
fac.GetLine(3, "blue").Draw(context,10,10);
Console.Write(context);
TriangleFactory fac2 = new TriangleFactory();
fac2.GetTriangle(3, "blue").Draw("cont", 1,2,1,3,4,5);
fac2.GetTriangle(3, "blue").Draw("cont", 2,3,4,5,6,7) ;
fac2.GetTriangle(3, "blue").Draw("cont", 5,6,7,8,9,1);
RectangleFactory fac3 = new RectangleFactory();
fac3.GetRectangle(4, "red", 3, 6).Draw("cont", 3, 4);
fac3.GetRectangle(5, "blue", 7, 8).Draw("cont", 1, 23);
fac3.GetRectangle(5, "blue", 3, 6).Draw("cont", 1,4);
Console.ReadKey();
}
}
// enum typeTriangle { rectangular, isosceles, equilateral, notright };
public interface IPolygon
{
void Draw();
}
class Line:IPolygon
{
float thickness;
string color;
public Line( float thickness, string color)
{
this.thickness = thickness;
this.color = color;
}
public void Draw(string context, float x, float y)
{
Console.WriteLine("Line is drow!! Position x="+x +"y = "+ y);
}
}
class Triangle:IPolygon
{
float thickness;
string color;
public Triangle(float thickness, string color)
{
this.thickness = thickness;
this.color = color;
}
public void Draw(string context, float x1, float y1, float x2,float y2,float x3,float y3)
{
Console.WriteLine( "Draw triangle! Position x1 = "+x1+"y1 = " +y1+"x2 = " + x2+ "y2 = " + y2 +"x3 = "+ x3 + "y3 = " +y3);
}
}
class Rectangle:IPolygon
{
float thickness;
string color;
float height;
float width;
public Rectangle(float thickness, string c, float h, float wi)
{
this.thickness = thickness;
this.color = c;
this.height = h;
this.width = wi;
}
public void Draw(string context, float x1, float y1)
{
Console.WriteLine(" Rectangle is draw!! Position down left x1 = " + x1 + " y1 = " + y1);
}
}
class LineFactory
{
private Hashtable lines = new Hashtable();
public Line GetLine(float thickness, string color)
{
string key = color +";"+ thickness;
Line line = lines[key] as Line;
if (line ==null)
{
line = new Line(thickness, color);
lines.Add(key, line);
}
return line;
}
}
class TriangleFactory
{
private Hashtable triangles = new Hashtable();
public Triangle GetTriangle(float thickness, string color)
{
string key = color + ";" + thickness ;
Triangle triangle = triangles[key] as Triangle;
if (triangle == null)
{
triangle = new Triangle(thickness, color);
triangles.Add(key, triangle);
//Console.WriteLine("Triangle done ! {0} === ccc {1} ==== type {2} ", thickness, color, type);
}
return triangle;
}
}
class RectangleFactory
{
private Hashtable rectangles = new Hashtable();
public Rektangle GetRectangle(float thickness, string color, float height, float width)
{
string key = color + ";" + thickness +";"+height+";"+width;
Rektangle rectangle = rectangles[key] as Rektangle;
if (rectangle == null)
{
rectangle = new Rektangle(thickness, color,height,width);
rectangles.Add(key, rectangle);
}
return rectangle;
}
}
}
}}
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.
}
Джерела
Література
- Будай, Андрій (2012). Дизайн-патерни — просто, як двері. с. 90.
- Алан Шаллоуей, Джеймс Р. Тротт. Шаблоны проектирования. Новый подход к объектно-ориентированному анализу и проектированию = Design Patterns Explained: A New Perspective on Object-Oriented Design. — М. : «Вильямс», 2002. — 288 с. — ISBN 0-201-71594-5.