Flyweight
Проблем
Да се разработи част от банкова система за съхраняване на информация за банкови карти.
Всяка карта има индивидуални данни: номер, собственик и срок на валидност. Освен това картите съдържат информация за доставчик, тип карта, валута, такса и дизайн.
Решение без използване на шаблона
Един възможен подход е всички данни да се съхраняват директно във всеки обект BankCard:
public class BankCard {
private final String cardNumber;
private final String owner;
private final String expirationDate;
private final String provider;
private final String cardType;
private final String currency;
private final double fee;
private final String design;
public BankCard(String cardNumber, String owner, String expirationDate,
String provider, String cardType, String currency,
double fee, String design) {
this.cardNumber = cardNumber;
this.owner = owner;
this.expirationDate = expirationDate;
this.provider = provider;
this.cardType = cardType;
this.currency = currency;
this.fee = fee;
this.design = design;
}
public String getCardInfo() {
return cardNumber + " - " + owner + " - "
+ expirationDate + " - "
+ provider + " " + cardType + ", "
+ currency + ", fee: " + fee
+ ", design: " + design;
}
}
Използване:
public class Application {
public static void main(String[] args) {
BankCard firstCard = new BankCard(
"1111-2222-3333-4444",
"John Smith",
"12/28",
"Visa",
"Debit",
"EUR",
2.50,
"Blue");
BankCard secondCard = new BankCard(
"5555-6666-7777-8888",
"Anna Brown",
"09/27",
"Visa",
"Debit",
"EUR",
2.50,
"Blue");
BankCard thirdCard = new BankCard(
"9999-0000-1111-2222",
"Peter Johnson",
"03/29",
"Mastercard",
"Credit",
"EUR",
4.00,
"Silver");
}
}
Недостатъци на решението
При този подход всяка карта съхранява както индивидуалните си данни, така и информацията, която е обща за много други карти.
В примера първата и втората карта имат различни номер, собственик и срок на валидност, но повтарят едни и същи стойности за доставчик, тип карта, валута, такса и дизайн.
При малък брой обекти това не е съществен проблем. При хиляди или милиони карти обаче съхраняването на една и съща информация във всеки обект води до излишно използване на памет и до дублиране на данни.
Следователно е необходимо решение, което отделя повтарящата се информация и позволява тя да бъде споделяна между много обекти.
Шаблонът като решение
Шаблонът Flyweight отделя споделеното състояние на обектите в самостоятелен обект, който може да бъде преизползван многократно.
Индивидуалното състояние остава извън flyweight обекта и се съхранява в контекста, който го използва.
Дефиниция
Flyweight е структурен шаблон за проектиране, който намалява броя на създаваните обекти чрез споделяне на общо, неизменяемо състояние между множество контексти.
Основни понятия:
- Flyweight - Споделен обект, който съдържа общото състояние.
- Intrinsic state - Вътрешно състояние, което се споделя и не зависи от конкретния контекст.
- Extrinsic state - Външно състояние, което е различно за всеки контекст и се подава или съхранява отделно.
- Flyweight Factory - Клас, който създава, кешира и преизползва flyweight обекти.
В примера шаблонът на картата (CardTemplate) е flyweight обект, а конкретната банкова карта (BankCard) е контекстът, който го използва.
UML диаграма
Примерна реализация
Flyweight
public class CardTemplate {
private final String provider;
private final String cardType;
private final String currency;
private final double fee;
private final String design;
public CardTemplate(String provider, String cardType, String currency,
double fee, String design) {
this.provider = provider;
this.cardType = cardType;
this.currency = currency;
this.fee = fee;
this.design = design;
}
public String getTemplateInfo() {
return provider + " " + cardType + ", "
+ currency + ", fee: " + fee
+ ", design: " + design;
}
}
Flyweight Factory
import java.util.HashMap;
import java.util.Map;
public class CardTemplateFactory {
private static final Map<String, CardTemplate> templates = new HashMap<>();
public static CardTemplate getTemplate(String provider, String cardType,
String currency, double fee,
String design) {
String key = provider + "-" + cardType + "-" + currency + "-" + design;
CardTemplate template = templates.get(key);
if (template == null) {
template = new CardTemplate(provider, cardType, currency, fee, design);
templates.put(key, template);
}
return template;
}
public static int getTemplatesCount() {
return templates.size();
}
}
Context
public class BankCard {
private final String cardNumber;
private final String owner;
private final String expirationDate;
private final CardTemplate template;
public BankCard(String cardNumber, String owner,
String expirationDate, CardTemplate template) {
this.cardNumber = cardNumber;
this.owner = owner;
this.expirationDate = expirationDate;
this.template = template;
}
public String getCardInfo() {
return cardNumber + " - " + owner + " - "
+ expirationDate + " - "
+ template.getTemplateInfo();
}
}
Използване
public class Application {
public static void main(String[] args) {
CardTemplate visaDebit =
CardTemplateFactory.getTemplate(
"Visa", "Debit", "EUR", 2.50, "Blue");
CardTemplate anotherVisaDebit =
CardTemplateFactory.getTemplate(
"Visa", "Debit", "EUR", 2.50, "Blue");
CardTemplate mastercardCredit =
CardTemplateFactory.getTemplate(
"Mastercard", "Credit", "EUR", 4.00, "Silver");
BankCard firstCard = new BankCard(
"1111-2222-3333-4444",
"John Smith",
"12/28",
visaDebit);
BankCard secondCard = new BankCard(
"5555-6666-7777-8888",
"Anna Brown",
"09/27",
anotherVisaDebit);
BankCard thirdCard = new BankCard(
"9999-0000-1111-2222",
"Peter Johnson",
"03/29",
mastercardCredit);
System.out.println(firstCard.getCardInfo());
System.out.println(secondCard.getCardInfo());
System.out.println(thirdCard.getCardInfo());
System.out.println(CardTemplateFactory.getTemplatesCount());
}
}
Класът CardTemplate съдържа общото състояние на картите: доставчик, тип карта, валута, такса и дизайн. Това състояние е еднакво за много карти и не зависи от конкретен клиент.
Класът BankCard съдържа индивидуалното състояние: номер на карта, собственик и срок на валидност. Освен това той съдържа референция към обект от тип CardTemplate.
Класът CardTemplateFactory отговаря за създаването и кеширането на шаблоните. Ако вече съществува шаблон със същите характеристики, фабриката връща съществуващия обект, вместо да създава нов.
В примера първата и втората карта използват един и същ шаблон Visa Debit, затова във фабриката се съхраняват само два шаблона, въпреки че са създадени три банкови карти.
[!IMPORTANT] Flyweight не споделя целите обекти, а само тяхното общо и неизменяемо състояние. Индивидуалните данни остават извън flyweight обекта и се съхраняват в контекста.
Предимства
- намалява броя на създаваните обекти;
- намалява използваната памет при голям брой сходни обекти;
- отделя общото от индивидуалното състояние;
- позволява преизползване на вече създадени обекти.
Недостатъци
- усложнява структурата на приложението;
- изисква ясно разграничаване между вътрешно и външно състояние;
- фабриката трябва да управлява кеша от споделени обекти;
- не е подходящ, ако обектите не споделят значителна част от състоянието си.
Приложение
Flyweight е подходящ когато:
- се създава голям брой сходни обекти;
- голяма част от състоянието на тези обекти се повтаря;
- споделеното състояние може да бъде отделено и направено неизменяемо;
- оптимизацията на паметта е важна за приложението.