1. Введение
В этом уроке мы познакомимся с одним из шаблонов креативного дизайна — шаблоном прототипа. Сначала мы объясним этот шаблон, а затем перейдем к его реализации на Java.
Мы также обсудим некоторые из его преимуществ и недостатков.
2. Образец прототипа
Шаблон прототипа обычно используется, когда у нас есть экземпляр класса (прототип) и мы хотим создать новые объекты, просто скопировав прототип .
Давайте воспользуемся аналогией, чтобы лучше понять этот шаблон.
В некоторых играх нам нужны деревья или здания на заднем плане. Мы можем понять, что нам не нужно создавать новые деревья или здания и отображать их на экране каждый раз, когда персонаж движется.
Итак, сначала мы создаем экземпляр дерева. Затем мы можем создать из этого экземпляра (прототипа) столько деревьев, сколько захотим, и обновить их позиции. Мы также можем изменить цвет деревьев для нового уровня в игре.
Шаблон Prototype очень похож. Вместо того, чтобы создавать новые объекты, нам просто нужно клонировать прототип.
3. UML-диаграмма
На диаграмме мы видим, что клиент говорит прототипу клонировать себя и создать объект. 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 .