Видео курс Шаблоны проектирования

Основные темы, рассматриваемые на уроке:
00:16 1 Клонирования (вступление)
01:18 2 Метафора
07:09 3 Структура паттерна на языке UML
07:43 4 Структура паттерна на языке C#
11:48 5 Прототипно ориентированное мышление (на примере C#)
20:43 6 Назначение паттерна
22:42 7 «Когда использовать паттерн?»

Видеоурок расскажет о паттерне Prototype, который предоставляет возможность создания новых объектов-продуктов (клонов), используя технику клонирования (копирования) созданного ранее объекта-оригинала-продукта (прототипа). Паттерн Prototype – позволяет задать различные виды (классы-виды) объектов-продуктов (клонов), через настройку состояния каждого нового созданного клона. Классификация клонов-продуктов производится на основании различия их состояний.

Для просмотра полной версии видеокурса и получения доступа к дополнительным учебным материалам Вам необходимо оформить подписку
Оформить подписку

Паттерн Prototype

Название

Прототип

Также известен как

-

Классификация

По цели: порождающий

По применимости: к объектам

Частота использования

Средняя               -   1 2 3 4 5

Назначение

Паттерн Prototype предоставляет возможность создания новых объектов-продуктов (клонов), используя технику клонирования (копирования) созданного ранее объекта-оригинала-продукта (прототипа). Паттерн Prototype – позволяет задать различные виды (классы-виды) объектов-продуктов (клонов), через настройку состояния каждого нового созданного клона. Классификация клонов-продуктов производится на основании различия их состояний.

Введение

Паттерн Prototype описывает процесс правильного создания объектов-клонов на основе имеющегося объекта-прототипа, другими словами, паттерн Prototype описывает правильные способы организации процесса клонирования.

Что такое клонирование в объективной реальности? В биологии термин «клонирование» - обозначает процессы копирования любых живых существ. Но, в информатике термин «клонирование» имеет свое специфическое значение, отличное от его значения в биологии. В биологии процесс клонирования происходит путем создания не готовой копии взрослого организма, а путем выращивания взрослого клона из младенца. Изначально клон-младенец порождается на основе генетического материала организма-прототипа, и развивается до взрослого состояния поэтапно, согласно заложенной в нем генетической программы развития. В информатике же клонирование происходит «мгновенно». В результате клонирования прототипа в информатике, получается сразу «взрослый» клон полностью идентичный прототипу.

В жизни программному клонированию аналогии не найти, так как биологический организм-клон никогда не будет идентичен своему организму-прототипу. Нарушение идентичности происходит за счет вмешательства в развитие клона внешних факторов среды его обитания. С потерей идентичности у биологического клона развивается ярко выраженная индивидуальность. В информатике же, легко добиться идентичности клона и прототипа за счет возможности копирования всего состояния прототипа в клон.

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

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

Структура паттерна на языке UML

См. пример к главе: \004_Prototype\001_Prototype

Структура паттерна на языке C#

См. пример к главе: \004_Prototype\001_Prototype

Участники

  • Prototype - Прототип:

Предоставляет интерфейс для клонирования себя.

  • ConcretePrototype - Конкретный прототип:

Реализует операцию клонирования себя.

  • Client - Клиент:

Клиент создает экземпляр прототипа. Вызывает на прототипе метод клонирования.

Отношения между участниками

Отношения между классами

  • Класс ConcretePrototype связан связью отношения наследования с абстрактным классом Prototype.

Отношения между объектами

  • Клиент вызывает на экземпляре-прототипе метод Clone и этот метод создает экземпляр-клон прототипа и возвращает ссылку на него.

Мотивация

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

Нота представляет собой графическое изображение звука. Нотная гамма содержит семь нот: «до», «ре», «ми», «фа», «соль», «ля», «си». Ноты распределяются по октавам.

Например, у пианино девять октав: семь полных и две неполных. В каждой октаве семь белых клавиш и пять черных, итого двенадцать клавиш в октаве. Каждая нота имеет длительность звучания: 1, 1/2, 1/4, 1/8, 1/16, 1/32 и 1/64. Соответственно с использованием пианино можно воспроизвести очень большое количество вариаций звуков. 

Таким образом может показаться, что потребуется создать по одному классу (class) для каждой вариации воспроизводимого звука. Но такой подход является нерациональным и неприемлемым. Более правильным решением проблемы - будет создание ноты-прототипа для ее последующего клонирования с возможностью настройки звучания (свойств) каждой ноты-клона.

Для того чтобы создать приложение с графическим интерфейсом, которое позволяло бы создавать и редактировать музыкальные композиции в наглядной форме, предлагается создать абстрактные классы Graphic и Tool. Класс Graphic служит для графического представления нот и нотных станов на форме приложения, а класс Tool определяет функциональные возможности для работы с графическими представлениями, такие как создание графического представления каждой ноты в зависимости от характеристик ноты (реализуется конкретным подклассом GraphicTool класса Tool), размещение его на нотном стане (реализуется конкретным подклассом RelocationTool класса Tool) и ориентирование штиля ноты (реализуется подклассом RotateTool класса Tool). 

Чтобы обеспечить гибкость и рациональность разработки приложения, класс GraphicTool будет создавать экземпляры классов MusicalNote (класс ноты) и Staff (класс музыкального стана), которые являются наследниками абстрактного класса Graphic и реализуют метод Clone базового класса путем клонирования прототипов экземпляров этих классов, переданных в качестве аргумента конструктора класса, и затем добавлять экземпляры-клоны в партитуру.

См. пример к главе: \004_Prototype\002_MusicalEditor

Применимость паттерна

Паттерн Prototype рекомендуется использовать, когда:

  • Программист не должен знать, как в системе создаются, компонуются и представляются объекты-продукты.
  • Классы, экземпляры которых требуется создать, определяются во время выполнения программы, например, с использованием техники позднего связывания (динамическая загрузка DLL).
  • Требуется избежать построения параллельных иерархий классов (фабрик или продуктов).
  • Экземпляры определенного класса могут иметь небольшое количество состояний. Тогда может оказаться удобнее установить прототип в соответствие каждому уникальному состоянию и в дальнейшем клонировать прототипы, а не создавать экземпляры вручную и не настраивать состояние этих экземпляров каждый раз заново.
  • Требуется создать некоторое количество экземпляров с одинаковым долго-вычисляемым состоянием. В этом случае для повышения производительности удобнее создать прототип и его клонировать, причем с использованием метода MemberwiseClone класса System.Object входящего в поставку Microsoft .NET Framework. Клонирование с использованием конструктора повышения производительности не даст. 
  • См. пример к главе: \004_Prototype\003_Performance

Результаты

Многие особенности применения паттерна Prototype совпадают с особенностями применения паттернов Abstract Factory и Builder:

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

Дополнительные особенности результатов применения паттерна Prototype:

  • Возможность добавления и удаления продуктов во время выполнения.

Паттерн Prototype позволяет добавлять новую разновидность «классов-продуктов» во время выполнения программы за счет конфигурирования состояния объекта-клона полученного в результате клонирования объекта-прототипа. «Класс-продукт» следует понимать не как конструкцию языка C# (class), а как «вид-продукт» получившийся за счет конфигурации состояния объекта-клона.

См. пример к главе: \004_Prototype\004_ObjectClass

  • Описание новых типов объектов (объектов-классов) путем изменения состояния клонов.

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

  • Уменьшение числа производных классов.

Большинство порождающих паттернов используют иерархии классов продуктов параллельные иерархиям классов фабрик. Паттерн Prototype через клонирование и настройку объектов-продуктов позволяет избавиться от наличия иерархии фабрик, порождающих продукты.

  • Динамическое добавление классов во время выполнения программы.

Платформа .Net позволяет динамически подключать новые классы к исполняющемуся приложению и создавать экземпляры этих классов (late binding - позднее связывание). Приложение, создающее экземпляры динамически загружаемого класса, не имеет возможности вызывать конструктор напрямую. Вместо этого на классе-объекте Activator вызывается метод CreateInstance, которому в качестве аргумента передается строковое представление полного квалифицированного имени класса, экземпляр которого требуется создать, например, Activator.CreateInstance("MyNamespace.MyClass"). Паттерн Prototype в определенных случаях может стать гибкой альтернативой позднему связыванию избавляя от многократного инстанцирования классов, подключаемых динамически. Достаточно создать один экземпляр-прототип и в дальнейшем его клонировать. Такой подход может дать выигрыш в производительности при необходимости создания большого количества однотипных экземпляров классов.

При клонировании могут возникнуть проблемы корректного клонирования ассоциаций. Особое внимание требуется обращать на клонирование двухсторонних ассоциаций.

Реализация

Использование паттерна Prototype может оказаться полезным в статически типизированных языках вроде C#, где классы не являются объектами. Меньше всего паттерн Prototype может оказаться полезным в прототипно-ориентированных языках вроде JavaScript, так как в основе таких языков уже заложена конструкция эквивалентная прототипу, это – «объект-класс». В прототипно-ориентированных языках создание любого объекта производится путем клонирования прототипа и такие языки базируются именно на идеях использования паттерна Prototype.

Вопросы, которые могут возникнуть при реализации прототипов:

  • Использование диспетчера прототипов.

Если в программе требуется создавать большое число прототипов и при этом заранее не известно сколько прототипов может понадобиться, то есть смысл использовать вспомогательный объект для создания прототипов и контроля над ними. Такой объект будет представлять собой реестр прототипов или его можно назвать - «диспетчер прототипов». Реестр прототипов – представляет собой ассоциативную коллекцию типа хеш-таблицы или словаря, в которой в соответствие ключам поставлены объекты-прототипы. Реестр может возвращать ссылку на прототип или непосредственно на построенный клон прототипа, это зависит от желаемой логики.

  • Реализация метода Clone.

Самый ответственный момент при использовании паттерна Prototype – реализация метода Clone. Особое внимание потребуется при наличии ассоциаций в клонируемом прототипе.

Паттерн Prototype, выражен в платформе .Net в виде техники клонирования, через использование реализации интерфейса ICloneable или метода MemberwiseClone класса System.Object. Но метод MemberwiseClone не решает проблему «глубокого и поверхностного клонирования». При клонировании с использованием метода MemberwiseClone следует учитывать следующие особенности:

Граф наследования всегда клонируется глубоко.

Могут возникнуть проблемы корректного клонирования ассоциаций. Так как ассоциации клонируются поверхностно. Перед клонированием следует ответить на вопрос: должны ли при клонировании объекта клонироваться также и другие объекты на которые ссылаются переменные клонируемого объекта, или клон будет использовать эти объекты совместно с оригиналом?

Особо внимание требуется обратить на клонирование двухсторонних ассоциаций.

См. пример к главе: \004_Prototype\005_Features

  • Инициализация клонов.

Часто, сразу после клонирования, требуется производить настройку (изменение) состояния объекта-клона (полностью или частично). Можно задуматься над тем, чтобы передавать параметры в метод Clone перед началом клонирования, но такой подход считается неудачным, так как мешает построению единообразного интерфейса клонирования. Для таких случаев, будет целесообразно ввести набор методов для установки или очистки важных элементов состояния, или ввести один метод Initialize который будет принимать параметры для установки состояния клона.

Пример кода игры «Лабиринт»

Определим класс MazePrototypeFactory, производный от класса MazeFactory, который был создан при рассмотрении паттерна Abstract Factory. Конструктор класса MazePrototypeFactory в качестве параметров будет принимать прототипы объектов классов Maze, Wall, Room и Door, из которых состоит лабиринт.

Класс MazePrototypeFactory будет создавать готовые объекты на основе соответствующих прототипов путем клонирования этих прототипов, для этого переопределим в нем фабричные методы MakeMaze, MakeRoom, MakeWall и MakeDoor из базового класса MazeFactory

class MazePrototypeFactory : MazeFactory
    {
        // Поля.
        Maze prototypeMaze = null;
        Room prototypeRoom = null;
        Wall prototypeWall = null;
        Door prototypeDoor = null;

        // Конструктор.
        public MazePrototypeFactory(Maze maze, Wall wall, Room room, Door door)
        {
            this.prototypeMaze = maze;
            this.prototypeWall = wall;
            this.prototypeRoom = room;
            this.prototypeDoor = door;
        }

        // Методы.

        public override Maze MakeMaze()
        {
            return prototypeMaze.Clone();
        }

        public override Room MakeRoom(int number)
        {
            Room room = prototypeRoom.Clone();
            room.Initialize(number);

            return room;
        }

        public override Wall MakeWall()
        {
            // Клонирование.
            return prototypeWall.Clone();
        }

        public override Door MakeDoor(Room room1, Room room2)
        {
            Door door = prototypeDoor.Clone();
            door.Initialize(room1, room2);

            return door;
        }
    }

Для создания экземпляра фабрики, позволяющей построить простой лабиринт, достаточно сконфигурировать объект класса MazePrototypeFactory объектами классов Maze, Wall, Room и Door, составляющими простой лабиринт. Для создания лабиринта требуется фабрику класса MazePrototypeFactory, передать в качестве аргумента метода CreateMaze класса MazeGame, который был создан при рассмотрении паттерна Abstract Factory.

       // Создаем генератор лабиринта.
       MazeGame game = new MazeGame();

       // Конфигурируем фабрику базовыми элементами лабиринта
       MazePrototypeFactory simpleMazeFactory = 
            new MazePrototypeFactory(new Maze(), new Wall(), new Room(), new Door());

       // Создаем лабиринт из двух комнат используя фабричный метод - CreateMaze().
       Maze maze = game.CreateMaze(simpleMazeFactory);

Поскольку экземпляр класса MazePrototypeFactory может быть сконфигурирован экземплярами классов производных от классов Maze, Wall, Room и Door, то отсутствует необходимость порождать новые классы производные от класса MazePrototypeFactory для создания специальных видов лабиринтов.

Для того чтобы изменить тип простого лабиринта на лабиринт с бомбами требуется организовать следующий вызов:

// Конфигурируем фабрику специальными элементами лабиринта
       MazePrototypeFactory bombedMazeFactory = new MazePrototypeFactory(new Maze(), new BombedWall(), new RoomWithBomb(), new Door());

Объекты классов типов Maze, Wall, Room и Door, которые предполагается использовать в качестве прототипов, должны содержать в себе реализацию метода Clone. 

class Door : MapSite
    {
        // Поля.
        Room room1 = null;
        Room room2 = null;
        bool isOpen;

        // Конструкторы.
        public Door(){}

        public Door(Door other)
        {
            this.room1 = other.room1;
            this.room2 = other.room2;
        }

        public Door(Room room1, Room room2)
        {
            this.room1 = room1;
            this.room2 = room2;
        }

        // Методы.
        // Отображает дверь.
        public override void Enter()
        {
            Console.WriteLine("Door");
        }


        // Метод возвращает ссылку на другую комнату.
        public Room OtherSideFrom(Room room)
        {
            if (room == room1)  // Если идем из r1 в r2, то возвращаем ссылку на r2.
                return room2;
            else          // Иначе: Если идем из r2 в r1, то возвращаем ссылку на r1.
                return room1;
        }

        // Клонирование.
        public virtual Door Clone()
        {
            Door door = new Door(this.room1, this.room2);
     // При обращении к закрытым членам экземпляра, в пределах того-же класса 
     // инкапсуляция не работает.
            door.isOpen = this.isOpen;
            return door;
        }

        // Метод инициализации...
        public virtual void Initialize(Room room1, Room room2)
        {
            this.room1 = room1;
            this.room2 = room2;
        }
    }

В подклассах классов Maze, Wall, Room и Door, нужно переопределить метод Clone.

class BombedWall : Wall
    {
        // Поля.
        bool bomb;

        // Конструкторы.
        public BombedWall(){}

        public BombedWall(BombedWall other)
        {
            this.bomb = other.bomb;
        }

        // Переопределение базовой операции клонирования.
        public override Wall Clone()
        {
            return new BombedWall(this);
        }

        public bool HasBomb()
        {
            return this.bomb;
        }
    }

При таком определении метода Clone в базовых классах Maze, Wall, Room и Door, клиент избавляется от необходимости знать о конкретных реализациях операции клонирования в подклассах этих классов, т.к. клиенту никогда не придется приводить значение, возвращаемое Clone, к базовому типу.

Известные применения паттерна в .Net

System.Data.DataSet

http://msdn.microsoft.com/ru-ru/library/system.data.dataset.aspx

System.Array

http://msdn.microsoft.com/ru-ru/library/system.array.aspx

System.Collections.ArrayList

http://msdn.microsoft.com/ru-ru/library/system.collections.arraylist.aspx

System.Collections.BitArray

http://msdn.microsoft.com/ru-ru/library/system.collections.bitarray.aspx

System.Collections.Hashtable

http://msdn.microsoft.com/ru-ru/library/system.collections.hashtable.aspx

System.Collections.Queue

http://msdn.microsoft.com/ru-ru/library/system.collections.queue.aspx

System.Collections.SortedList

http://msdn.microsoft.com/ru-ru/library/system.collections.sortedlist.aspx

System.Collections.Stack

http://msdn.microsoft.com/ru-ru/library/system.collections.stack.aspx

System.Data.DataTable

http://msdn.microsoft.com/ru-ru/library/system.data.datatable.aspx

System.Data.SqlClient.SqlCommand

http://msdn.microsoft.com/ru-ru/library/system.data.sqlclient.sqlcommand(v=vs.100).aspx

System.Delegate

http://msdn.microsoft.com/ru-ru/library/system.delegate.aspx

System.Reflection.AssemblyName

http://msdn.microsoft.com/ru-ru/library/system.reflection.assemblyname.aspx

System.Text.Encoding

http://msdn.microsoft.com/ru-ru/library/system.text.encoding(v=vs.90).aspx

System.Xml.XmlNode

http://msdn.microsoft.com/ru-ru/library/system.xml.xmlnode.aspx

System.Globalization.Calendar

http://msdn.microsoft.com/ru-ru/library/system.globalization.calendar.aspx

System.Globalization.DateTimeFormatInfo

http://msdn.microsoft.com/ru-ru/library/system.globalization.datetimeformatinfo(v=vs.110).aspx

System.Globalization.TextInfo

http://msdn.microsoft.com/ru-ru/library/system.globalization.textinfo.aspx

System.String

http://msdn.microsoft.com/ru-ru/library/system.string.aspx

И т.д.

© 2017 ITVDN, все права защищены