Автор оригинала: Vivek Balasubramaniam.
1. введение
В этом уроке мы познакомимся с одним из шаблонов творческого проектирования – шаблоном прототипа. Сначала мы объясним этот шаблон, а затем приступим к его реализации на Java.
Мы также обсудим некоторые его преимущества и недостатки.
2. Образец прототипа
Шаблон прототипа обычно используется , когда у нас есть экземпляр класса (прототип), и мы хотели бы создать новые объекты, просто скопировав прототип .
Давайте воспользуемся аналогией, чтобы лучше понять эту закономерность.
В некоторых играх нам нужны деревья или здания на заднем плане. Мы можем понять, что нам не нужно создавать новые деревья или здания и отображать их на экране каждый раз, когда персонаж движется.
Итак, сначала мы создаем экземпляр дерева. Затем мы можем создать из этого экземпляра (прототипа) столько деревьев, сколько захотим, и обновить их позиции. Мы также можем изменить цвет деревьев для нового уровня в игре.
Шаблон прототипа очень похож. Вместо создания новых объектов нам просто нужно клонировать прототипический экземпляр.
3. Диаграмма UML
На диаграмме мы видим, что клиент говорит прототипу клонировать себя и создать объект. Prototype является интерфейсом и объявляет метод для клонирования самого себя. ConcretePrototype1 и ConcretePrototype2 реализуют операцию клонирования самих себя.
4. Реализация
Одним из способов реализации этого шаблона в Java является использование метода clone () . Для этого мы реализуем интерфейс Cloneable .
Когда мы пытаемся клонировать, мы должны решить, делать ли мелкую или глубокую копию . В конце концов, все сводится к требованиям.
Например, если класс содержит только примитивные и неизменяемые поля , мы можем использовать неглубокую копию.
Если он содержит ссылки на изменяемые поля, мы должны перейти к глубокой копии . Мы могли бы сделать это с помощью |/конструкторов копирования или сериализации и десериализации .
Давайте возьмем пример, о котором мы упоминали ранее, и перейдем к тому, как применить шаблон прототипа без использования интерфейса Cloneable . Для этого давайте создадим абстрактный класс с именем Дерево с помощью абстрактного метода ‘copy’ .
public abstract class Tree { // ... public abstract Tree copy(); }
Теперь предположим, что у нас есть две разные реализации Дерева под названием Пластиковое дерево и Сосна :
public class PlasticTree extends Tree { // ... @Override public Tree copy() { PlasticTree plasticTreeClone = new PlasticTree(this.getMass(), this.getHeight()); plasticTreeClone.setPosition(this.getPosition()); return plasticTreeClone; } }
public class PineTree extends Tree { // ... @Override public Tree copy() { PineTree pineTreeClone = new PineTree(this.getMass(), this.getHeight()); pineTreeClone.setPosition(this.getPosition()); return pineTreeClone; } }
Итак, здесь мы видим, что классы, которые расширяют Дерево и реализуют метод copy , могут выступать в качестве прототипов для создания копии самих себя.
Шаблон прототипа также позволяет нам создавать копии объектов, не зависящие от конкретных классов . Допустим, у нас есть список деревьев, и мы хотели бы создать их копии. Благодаря полиморфизму мы можем легко создавать несколько копий, не зная типов деревьев.
5. Тестирование
Теперь давайте проверим это:
public class TreePrototypesUnitTest { @Test public void givenAPlasticTreePrototypeWhenClonedThenCreateA_Clone() { // ... PlasticTree plasticTree = new PlasticTree(mass, height); plasticTree.setPosition(position); PlasticTree anotherPlasticTree = (PlasticTree) plasticTree.copy(); anotherPlasticTree.setPosition(otherPosition); assertEquals(position, plasticTree.getPosition()); assertEquals(otherPosition, anotherPlasticTree.getPosition()); } }
Мы видим, что дерево было клонировано из прототипа, и у нас есть два разных экземпляра Пластикового дерева . Мы только что обновили позицию в клоне и сохранили другие значения.
Теперь давайте клонируем список деревьев:
@Test public void givenA_ListOfTreesWhenClonedThenCreateListOfClones() { // create instances of PlasticTree and PineTree Listtrees = Arrays.asList(plasticTree, pineTree); List treeClones = trees.stream().map(Tree::copy).collect(toList()); // ... assertEquals(height, plasticTreeClone.getHeight()); assertEquals(position, plasticTreeClone.getPosition()); }
Обратите внимание, что мы можем сделать глубокую копию списка здесь, не завися от конкретных реализаций дерева .
6. Преимущества и недостатки
Этот шаблон удобен, когда наш новый объект лишь немного отличается от существующего. В некоторых случаях экземпляры могут иметь только несколько комбинаций состояний в классе. Поэтому вместо создания новых экземпляров мы можем заранее создать экземпляры с соответствующим состоянием, а затем клонировать их, когда захотим .
Иногда мы можем столкнуться с подклассами, которые отличаются только своим состоянием. Мы можем устранить эти подклассы, создав прототипы с начальным состоянием, а затем клонировав их.
Шаблон прототипа, как и любой другой шаблон дизайна, следует использовать только тогда, когда это уместно. Поскольку мы клонируем объекты, процесс может усложниться, когда существует много классов, что приведет к беспорядку. Кроме того, трудно клонировать классы, имеющие циклические ссылки.
7. Заключение
В этом уроке мы изучили ключевые понятия шаблона прототипа и увидели, как его реализовать на Java. Мы также обсудили некоторые из его плюсов и минусов.
Как обычно, исходный код этой статьи доступен на Github .