Паттерны: Абстрактная фабрика

Эта статья посвящена распространенному паттерну проектирования – Абстрактной фабрике. Мы немного коснемся общей теории по данному вопросу, подробно рассмотрим метафору, которая поможет понять суть этого паттерна, и изучим пример реализации Абстрактной фабрики на языке C#.

В рамках данного цикла статей я не ставлю своей целью заново описать паттерны проектирования, это уже было сделано много раз людьми, которые очень хорошо разбираются в этой области, да и в принципе информации по паттернам в Интернете достаточно много. Я просто их изучаю и делюсь своими наработками с вами. У Макконнелла в книге “Совершенный код”, в одной из первых глав, большое внимание уделено метафорам в разработке программного обеспечения, там он довольно подробно проходится по этому вопросу и обращает наше внимание на то, что хорошая метафора помогает быстро уловить суть задачи/проекта/технологии, плохая же может, наоборот, создать ошибочное представление. Вот и мне захотелось сделать такие метафоры для паттернов, которые во-первых не противоречат истине – являются корректными, во-вторых интуитивно понятны, в третьих легко запоминаемы. Может вы слышали о мнемотехнике – методологии, которая позволяет запоминать длинные ряды цифр, имена, события и т.п., она основана на том, что мы создаем образы и формируем связи между образами, что является естественным для нашего мозга. Вот и здесь я попытаюсь сделать также. ОГРОМНАЯ ПРОСЬБА!!! Если вдруг вы увидите ошибку или неточность в моих рассуждениях и аналогиях обязательно напишите!

Каждый паттерн будет разбираться с трех сторон: общая теория, метафора, пример реализации.

Абстрактная фабрика

Первый паттерн, который бы я хотел разобрать – это Абстрактная фабрика (Abstract factory). Он входит в группу порождающих паттернов, т.е. он создает что-то, что можно потом использовать. С теоретической стороны, он довольно хорошо описан в книге “Банды четырех” (Эрих Гамма, Ричард Хелм, Ральф Джонсон, Джон Влиссидес. “Приемы объектно-ориентированного проектирования. Паттерны проектирования), в википедии приведены примеры кода на разных языках программирования. Мы в свою очередь тоже приведем пример приложения, которое использует Абстрактную фабрику, и на нем разберем основные моменты, связанные с данным паттерном.

Теория из различных источников

Что мы знаем об Абстрактной фабрике из существующих источников знаний?! Назначение данного паттерна, если цитировать википедию, состоит в следующем: “Абстрактная фабрика предоставляет интерфейс для создания семейств взаимосвязанных или взаимозависимых объектов, не специфицируя их конкретных классов”. Для лучшего понимания происходящего нам пригодиться диаграмма (из той же википедии).

Модель абстрактной фабрики

В общем суть заключается в том, что у нас есть клиент, который желает создавать объекты, но при этом он ничего не хочет знать о реализации классов, используемых для создания объектов. Данные классы как-то идейно связаны между собой. В своей работе клиент использует только интерфейсы. Основным инструментом для него является интерфейс Абстрактной фабрики, которая содержит методы для создания нужных объектов, в дальнейшем, для работы с ними (объектами), клиент использует интерфейсы, реализованные в соответствующих классах. На базе Абстрактной фабрики создаются Конкретные фабрики, вот они то и передаются клиенту для использования. В качестве классического примера применения Абстрактной фабрики приводят использование различных реализаций графического представления элементов интерфейса (UI), когда внешний вид типовых элементы – кнопки, текстовые поля и т.п., определяется графической библиотекой/framework’ом (Motif, wxWidgets). В зависимости от используемой графической библиотеки на уровне приложения или операционной системы, клиенту, формирующему пользовательский интерфейс, будет передана та или иная Конкретная фабрика, через которую он (клиент) будет получать нужные ему элементы интерфейса, но при этом его совершенно не заботит, как они будут выглядеть.

Метафора

В качестве метафоры, которая позволит пролить свет на данный паттерн, мне показалась уместной следующая.

Метафора абстрактной фабрики

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

Так вот, клиентом в данном случае является фактически сам кран с системой. Клиент наливает напиток в стакан, конкретный напиток – это создаваемый объект. Клиенту все равно, что это будет за объект! Для того, чтобы из крана лилась та или иная газированная вода, требуется подключить к системе нужную емкость, которая, в нашем примере, является Конкретной фабрикой. Эти фабрики позволяют нам получить объекты (газ. воду), созданные в соответствии с определенным рецептом (классом). А вот возможность подключить к данному крану с системой конкретную емкость обеспечивает СТАНДАРТ, в соответствии с которым все изготовлено (размеры штуцеров, резьбы гаек, размеры емкостей и т.п.), вот этот самый стандарт и есть Абстрактная фабрика. Если нам привезут баллон (конкретная фабрика), выполненный не по стандарту (абстрактная фабрика) клиента (кран + система), то клиент просто не сможет подключить такую емкость и использовать ее.

Программная реализация Абстрактной фабрики на языке C#

Для тренировки будем использовать язык C#, а чтобы было немного веселее, сделаем графическое приложение с использованием Windows FormsПроект, который далее будет разбираться находится на github.

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

Внешний вид формы

Суть его очень проста: в выпадающем списке вы выбираете цвет, например Red (Красный). Потом нажимаете кнопку Build, и на рабочем поле программы будет нарисован квадрат и круг, закрашенные выбранным цветом. Если нажать на Clear, то поле будет очищено.

Нажата кнопка Build

Этот проект сделан в Microsoft Visual Studio 2017 Community Edition. Если вы используете другу IDE, то перенести нужные файлы и собрать проект, я надеюсь, вы сможете.

Структура проекта выглядит вот так.

Структура примера

Перечислим файлы, входящие в него:

 

Имя файла Описание
IAbstractFactory.cs Интерфейс Абстрактной фабрики
ConcreteFactoryGreen.cs Конкретная фабрика, создающая зеленые фигуры
ConcreteFactoryRed.cs Конкретная фабрика, создающая красные фигуры
ICircle.cs Интерфейс для работы с фигурой круг
ISquare.cs Интерфейс для работы с фигурой квадрат
CircleRed.cs, CircleGreen.cs Классы, реализующие красный и зеленый круг
SquareRed.cs, SquareGreen.cs Классы, реализующие красный и зеленый квадрат

Клиентом, в данном случае, выступает форма (Form.cs), а точнее один из ее методов (об этом чуть ниже).

Фабрики у нас лежат в пространстве имен Factory, а классы фигур – Figure.

Для начала посмотрим на Абстрактную фабрику.

Файл IAbstractFactory.cs

using AbstractFactoryExample.Figure;
namespace AbstractFactoryExample.Factory
{
    interface IAbstractFactory
    {
        ISquare CreateSquare();
        ICircle CreateCircle();        
    }
}

Это интерфейс, определяющий два метода, которые нужно будет реализовать в конкретных фабриках, создающие квадрат и круг и возвращающие объекты, поддерживающие интерфейсы ISquare и ICircle.

Теперь перейдем к конкретным фабрикам, посмотрим на ту, что создает красные фигуры.

Файл ConcreteFactoryRed.cs

using AbstractFactoryExample.Figure;
namespace AbstractFactoryExample.Factory
{
    class ConcreteFactoryRed : IAbstractFactory
    {
        public ConcreteFactoryRed()
        {
        }
        #region IAbstractFactory interface
        public ICircle CreateCircle()
        {
            return new Figure.CircleRed();
        }
        public ISquare CreateSquare()
        {
            return new Figure.SquareRed();
        }
        #endregion IAbstractFactory interface
    }
}

Фабрика, создающая зеленые фигуры (файл ConcreteFactoryGreen.cs) выглядит практически также, поэтому приводить мы ее не будем.

В рамках Конкретной фабрики мы реализовали методы интерфейса IAbstractFactory, возвращающие нужные нам объекты.

Теперь перейдем к классам фигур. Для начала нужно создать интерфейсы для работы с фигурами.

Для круга.

Файл ICircle.cs

using System.Drawing;
namespace AbstractFactoryExample.Figure
{
    interface ICircle
    {
        int GetRadius();
        bool SetRadius(int radius);
        Color GetCirleColor();
    }
}

И для квадрата.

Файл ISquare.cs

using System.Drawing;
namespace AbstractFactoryExample.Figure
{
    interface ISquare
    {
        int GetSideSize();
        bool SetSideSize(int sideSize);
        Color GetSqureColor();
    }
}

Интерфейс круга позволяет получать и задавать радиус и получать цвет, квадрата – получать и задавать длину стороны и получать цвет.

Реализации классов красных кругов и квадратов представлены ниже.

Файл CircleRed.cs

using System.Drawing;
namespace AbstractFactoryExample.Figure
{
    class CircleRed : ICircle
    {
        private readonly Color circleColor = Color.Red;
        private int radius;
        
        public CircleRed()
        {
            radius = 25;
        }
        
        #region ICircle interface
        public Color GetCirleColor()
        {
            return circleColor;
        }
        public int GetRadius()
        {
            return radius;
        }
        public bool SetRadius(int radius)
        {
            if(radius > 0)
            {
                this.radius = radius;
                return true;
            }
            else
            {
                return false;
            }
        }
        #endregion ICircle interface
    }
}

Файл SquareRed.cs

using System.Drawing;
namespace AbstractFactoryExample.Figure
{
    class SquareRed : ISquare
    {
        private int sideSize;
        private readonly Color color = Color.Red;
        public SquareRed()
        {
            sideSize = 50;
        }
        #region ISquare interface
        public int GetSideSize()
        {
            return sideSize;
        }
        public Color GetSqureColor()
        {
            return color;
        }
        public bool SetSideSize(int sideSize)
        {
            if(sideSize > 0)
            {
                this.sideSize = sideSize;
                return true;
            }
            else
            {
                return false;
            }
        }
        #endregion ISquare interface
    }
}

Как вы могли заметить, цвет задается переменной color и она, во-первых readonly, во-вторых у нас нет методов, которые позволили бы его менять.

Перейдем к клиенту, как мы уже определились выше – это форма (Form.cs). В ней мы должны создать две конкретные фабрики.

private IAbstractFactory factoryRed;
private IAbstractFactory factoryGreen;

Инициализируем их (сделаем это в Form1_Load()).

private void Form1_Load(object sender, EventArgs e)
{
    cbFigType.SelectedIndex = 0;
    g = this.CreateGraphics();
    factoryRed = new ConcreteFactoryRed();
    factoryGreen = new ConcreteFactoryGreen();
}

При нажатии на кнопку Build вызывается tbBuild_Click(), содержание которого выглядит так.

private void tbBuild_Click(object sender, EventArgs e)
{
    switch(cbFigType.SelectedItem)
    {
         case "Red": DrawFigure(factoryRed);
                     break;
         case "Green": DrawFigure(factoryGreen);
                     break;
    }
}

В зависимости от того, какой цвет выбран, вызывается метод DrawFigure() с той или иной конкретной фабрикой, создающей объекты нужно цвета.

Взглянем на метод DrawFigure().

private void DrawFigure(IAbstractFactory factory)
{
    Figure.ICircle circle = factory.CreateCircle();
    Figure.ISquare square = factory.CreateSquare();
    SolidBrush brush = new SolidBrush(square.GetSqureColor());
    g.FillRectangle(brush, new Rectangle(100, 100, square.GetSideSize(), square.GetSideSize()));
    g.FillEllipse(brush, new Rectangle(250, 100, circle.GetRadius() * 2, circle.GetRadius() * 2));
    brush.Dispose();
}

Этот метод принимает в качестве аргумента Абстрактную фабрику и с помощью нее создает фигуры – круг и квадрат. После этого отрисовывает их на экране, используя свойства данных фигур (размеры и цвет).

На этом закончим наше обсуждение Абстрактной фабрики. Надеюсь было понятно. Если что, пишите пожалуйста комментарии.

Вот ещё несколько книжек в тему:

Книга «Приемы объектно-ориентированного проектирования. Паттерны проектирования» Э. Гамма, Р. Хелм, Р. Джонсон, Дж. Влиссидес - купить на OZON.ru книгу Design Patterns: Elements of Reusable Object-Oriented Software с быстрой доставкой | 978-5-459-01720-5 Книга «Приемы объектно-ориентированного проектирования. Паттерны проектирования» Э. Гамма, Р. Хелм, Р. Джонсон, Дж. Влиссидес
Книга «Паттерны проектирования» Эрик Фримен, Элизабет Фримен, Кэтти Сьерра, Берт Бейтс - купить на OZON.ru книгу Head First Design Patterns с быстрой доставкой | 978-5-4461-0106-1 Книга «Паттерны проектирования» Эрик Фримен, Элизабет Фримен, Кэтти Сьерра, Берт Бейтс

 

Паттерны: Абстрактная фабрика: 6 комментариев

  1. Dima

    Для начала, паттерн Абстрактная фабрика предлагает выделить общие интерфейсы для отдельных продуктов, составляющих семейства. Так, все вариации кресел получат общий интерфейс

  2. Juniors

    Измените код инициализации программы так, чтобы она создавала определённую фабрику и передавала её в клиентский код.

Добавить комментарий

Ваш адрес email не будет опубликован.