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

Примерна реализация

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 е подходящ когато:

  • се създава голям брой сходни обекти;
  • голяма част от състоянието на тези обекти се повтаря;
  • споделеното състояние може да бъде отделено и направено неизменяемо;
  • оптимизацията на паметта е важна за приложението.

This site uses Just the Docs, a documentation theme for Jekyll.