Приспособленец (Flyweight)

Последнее обновление: 31.10.2015

Паттерн Приспособленец (Flyweight) - структурный шаблон проектирования, который позволяет использовать разделяемые объекты сразу в нескольких контекстах. Данный паттерн используется преимущественно для оптимизации работы с памятью.

В качестве стандартного применения данного паттерна можно привести следующий пример. Текст состоит из отдельных символов. Каждый символ может встречаться на одной странице текста много раз. Однако в компьютерной программе было бы слишком накладно выделять память для каждого отдельного символа в тексте. Гораздо проще было бы определить полный набор символов, например, в виде таблицы из 128 знаков (алфавитно-цифровые символы в разных регистрах, знаки препинания и т.д.). А в тексте применить этот набор общих разделяемых символов, вместо сотен и тысяч объектов, которые могли бы использоваться в тексте. И как следствие подобного подхода будет уменьшение количества используемых объектов и уменьшение используемой памяти.

Паттерн Приспособленец следует применять при соблюдении всех следующих условий:

  • Когда приложение использует большое количество однообразных объектов, из-за чего происходит выделение большого количества памяти

  • Когда часть состояния объекта, которое является изменяемым, можно вынести во вне. Вынесение внешнего состояния позволяет заменить множество объектов небольшой группой общих разделяемых объектов.

Ключевым моментом здесь является разделение состояния на внутренне и внешнее. Внутреннее состояние не зависит от контекста. В примере с символами внутреннее состояние описывается кодом символа из таблицы кодировки. Так как внутреннее состояние не зависит от контекста, то оно может быть разделяемым и поэтому выносится в разделяемые объекты.

Внешнее состояние зависит от контекста, является изменчивым. В применении к символам внешнее состояние может представлять положение символа на странице. То есть код символа может быть использован многими символами, тогда как положение на странице будет для каждого символа индивидуально.

При создании приспособленца внешнее состояние выносится. В приспособленце остается только внутреннее состояние. То есть в примере с символами приспособленец будет хранить код символа.

Отношения в данном паттерне можно описать следующей схемой:

Паттерн Приспособленец (Flyweight) в C# и .NET

Формальное определение паттерна на C#:

class FlyweightFactory
{
    Hashtable flyweights = new Hashtable();
    public FlyweightFactory()
    {
        flyweights.Add("X", new ConcreteFlyweight());
        flyweights.Add("Y", new ConcreteFlyweight());
        flyweights.Add("Z", new ConcreteFlyweight());
    }
    public Flyweight GetFlyweight(string key)
    {
        if (!flyweights.ContainsKey(key))
            flyweights.Add(key, new ConcreteFlyweight());
        return flyweights[key] as Flyweight;
    }
}

abstract class Flyweight
{
    public abstract void Operation(int extrinsicState);
}

class ConcreteFlyweight : Flyweight
{
    int intrinsicState;
    public override void Operation(int extrinsicState)
    {
    }
}

class UnsharedConcreteFlyweight : Flyweight
{
    int allState;
    public override void Operation(int extrinsicState)
    {
        allState = extrinsicState;
    }
}

class Client
{
    void Main()
    {
        int extrinsicstate = 22;

        FlyweightFactory f = new FlyweightFactory();

        Flyweight fx = f.GetFlyweight("X");
        fx.Operation(--extrinsicstate);

        Flyweight fy = f.GetFlyweight("Y");
        fy.Operation(--extrinsicstate);

        Flyweight fd = f.GetFlyweight("D");
        fd.Operation(--extrinsicstate);

        UnsharedConcreteFlyweight uf = new UnsharedConcreteFlyweight();

        uf.Operation(--extrinsicstate);
    }
}

Участники

  • Flyweight: определяет интерфейс, через который приспособленцы-разделяемые объекты могут получать внешнее состояние или воздействовать на него

  • ConcreteFlyweight: конкретный класс разделяемого приспособленца. Реализует интерфейс, объявленный в типе Flyweight, и при необходимости добавляет внутреннее состояние. Причем любое сохраняемое им состояние должно быть внутренним, не зависящим от контекста

  • UnsharedConcreteFlyweight: еще одна конкретная реализация интерфейса, определенного в типе Flyweight, только теперь объекты этого класса являются неразделяемыми

  • FlyweightFactory: фабрика приспособленцев - создает объекты разделяемых приспособленцев. Так как приспособленцы разделяются, то клиент не должен создавать их напрямую. Все созданные объекты хранятся в пуле. В примере выше для определения пула используется объект Hashtable, но это не обязательно. Можно применять и другие классы коллекций. Однако в зависимости от сложности структуры, хранящей разделяемые объекты, особенно если у нас большое количество приспособленцев, то может увеличиваться время на поиск нужного приспособленца - наверное это один из немногих недостатков данного паттерна.

    Если запрошенного приспособленца не оказалось в пуле, то фабрика создает его.

  • Client: использует объекты приспособленцев. Может хранить внешнее состояние и передавать его в качестве аргументов в методы приспособленцев

Рассмотрим пример. Допустим, мы проектируем программу для моделирования города. Город состоит из отдельных домов, поэтому нам надо создать объекты этих домов. Однако домов в городе может быть множество: сотни, тысячи. Они могут иметь разный вид, отличаться по различным признакам. Однако, как правило, многие дома делаются по стандартным проектам. И фактически мы можем выделить несколько типов домов, например, пятиэтажные кирпичные хрущевки, многоэтажные панельные высотки и так далее.

Используя некоторый анализ, мы можем выделить внутренне состояния домов и внешнее. К внутреннему состоянию, например, может относиться количество этажей, материал (кирпичи, панели и т.д.), или те показатели, которые определены его шаблоном, планом проектирования. К внешнему состоянию может относиться положение дома на географической карте, то есть его координаты, цвет дома, и так далее, то есть такие показатели, которые для каждого отдельного дома могут быть относительно индивидуальны.

В этом случае реализация строительства домов на C# с применением паттерна Flyweight могла бы выглядеть следующим образом:

class Program
{
    static void Main(string[] args)
    {
        double longitude = 37.61;
        double latitude = 55.74;

        HouseFactory houseFactory = new HouseFactory();
        for (int i = 0; i < 5;i++)
        {
            House panelHouse = houseFactory.GetHouse("Panel");
            if (panelHouse != null)
                panelHouse.Build(longitude, latitude);
            longitude += 0.1;
            latitude += 0.1;
        }

        for (int i = 0; i < 5; i++)
        {
            House brickHouse = houseFactory.GetHouse("Brick");
            if (brickHouse != null)
                brickHouse.Build(longitude, latitude);
            longitude += 0.1;
            latitude += 0.1;
        }

        Console.Read();
    }
}

abstract class House
{
    protected int stages; // количество этажей

    public abstract void Build(double longitude, double latitude);
}

class PanelHouse : House 
{
    public PanelHouse()
    {
        stages = 16;
    }

    public override void Build(double longitude, double latitude)
    {
        Console.WriteLine("Построен панельный дом из 16 этажей; координаты: {0} широты и {1} долготы", 
            latitude, longitude);
    }
}
class BrickHouse : House
{
    public BrickHouse()
    {
        stages = 5;
    }

    public override void Build(double longitude, double latitude)
    {
        Console.WriteLine("Построен кирпичный дом из 5 этажей; координаты: {0} широты и {1} долготы",
            latitude, longitude);
    }
}

class HouseFactory
{
    Dictionary<string, House> houses = new Dictionary<string, House>();
    public HouseFactory()
    {
        houses.Add("Panel", new PanelHouse());
        houses.Add("Brick", new BrickHouse());
    }

    public House GetHouse(string key)
    {
        if (houses.ContainsKey(key))
            return  houses[key];
        else
            return null;
    }
}

В качестве интерфейса приспособленца выступает абстрактный класс House, который определяет переменную stages - количество этажей, поскольку количество этажей относится к внутреннему состоянию, которое присуще всем домам. И также определяется метод Build(), который в качестве параметра принимает широту и долготу расположения дома - внешнее состояние.

Конкретные классы разделяемых приспособленцев - PanelHouse и BrickHouse отвечают за построение конкретных типов домов. Поскольку архитектурный план проектирования может точно задавать количество этажей для определенного типа дома, то в данном случае количество этажей устанавливается в конструкторе.

Фабрика HouseFactory создает два объекта дома для каждого конкретного приспособленца и возвращает их в методе GetHouse() в зависимости от параметра.

В роли клиента выступает класс Program, который задает начальные широту и долготу - внешнее состояние домов и использует фабрику для создания домов. Причем в реальности мы будем оперировать всего лишь двумя объектами, которые будут храниться в словаре в HouseFactory.

Помощь сайту
Юмани:
410011174743222
Перевод на карту
Номер карты:
4048415020898850