Перейти к основному содержимому

Шаблон прототипа в Java

· 4 мин. чтения

1. Введение

В этом уроке мы познакомимся с одним из шаблонов креативного дизайна — шаблоном прототипа. Сначала мы объясним этот шаблон, а затем перейдем к его реализации на Java.

Мы также обсудим некоторые из его преимуществ и недостатков.

2. Образец прототипа

Шаблон прототипа обычно используется, когда у нас есть экземпляр класса (прототип) и мы хотим создать новые объекты, просто скопировав прототип .

Давайте воспользуемся аналогией, чтобы лучше понять этот шаблон.

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

Итак, сначала мы создаем экземпляр дерева. Затем мы можем создать из этого экземпляра (прототипа) столько деревьев, сколько захотим, и обновить их позиции. Мы также можем изменить цвет деревьев для нового уровня в игре.

Шаблон Prototype очень похож. Вместо того, чтобы создавать новые объекты, нам просто нужно клонировать прототип.

3. UML-диаграмма

./22347f6745c3bbfc39551ade6fecf301.png

На диаграмме мы видим, что клиент говорит прототипу клонировать себя и создать объект. Prototype представляет собой интерфейс и объявляет метод клонирования самого себя. ConcretePrototype1 и ConcretePrototype2 реализуют операцию клонирования самих себя.

4. Реализация

Один из способов реализации этого шаблона в Java — использование метода clone() . Для этого мы реализуем интерфейс Cloneable .

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

Например, если класс содержит только примитивные и неизменяемые поля , мы можем использовать поверхностную копию.

Если он содержит ссылки на изменяемые поля, мы должны сделать глубокую копию . Мы могли бы сделать это с помощью конструкторов копирования или сериализации и десериализации .

Давайте возьмем пример, который мы упоминали ранее, и перейдем к тому, как применить шаблон Prototype без использования интерфейса Cloneable . Для этого создадим абстрактный класс Tree с абстрактным методом copy .

public abstract class Tree {

// ...
public abstract Tree copy();

}

Теперь предположим, что у нас есть две разные реализации Tree с именами PlasticTree и PineTree :

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;
}
}

Итак, здесь мы видим, что классы, которые расширяют Tree и реализуют метод копирования , могут действовать как прототипы для создания собственной копии.

Шаблон прототипа также позволяет нам создавать копии объектов независимо от конкретных классов . Допустим, у нас есть список деревьев, и мы хотели бы создать их копии. Благодаря полиморфизму мы можем легко создавать несколько копий, не зная типов деревьев.

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());
}
}

Мы видим, что дерево было клонировано из прототипа, и у нас есть два разных экземпляра PlasticTree . Мы только что обновили позицию в клоне и сохранили остальные значения.

Теперь давайте клонируем список деревьев:

@Test
public void givenA_ListOfTreesWhenClonedThenCreateListOfClones() {

// create instances of PlasticTree and PineTree

List<Tree> trees = Arrays.asList(plasticTree, pineTree);
List<Tree> treeClones = trees.stream().map(Tree::copy).collect(toList());

// ...

assertEquals(height, plasticTreeClone.getHeight());
assertEquals(position, plasticTreeClone.getPosition());
}

Обратите внимание, что здесь мы можем сделать глубокую копию списка, не завися от конкретных реализаций Tree.

6. Преимущества и недостатки

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

Иногда мы можем столкнуться с подклассами, которые отличаются только своим состоянием. Мы можем устранить эти подклассы, создав прототипы с начальным состоянием, а затем клонировав их.

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

7. Заключение

В этом руководстве мы изучили ключевые концепции шаблона Prototype и увидели, как реализовать его на Java. Мы также обсудили некоторые из его плюсов и минусов.

Как обычно, исходный код этой статьи доступен на Github .